(function () {
// Copyright (c) 2009-2012 Turbulenz Limited
/*global Float32Array: false*/
/*global TurbulenzEngine: false*/
/*global debug: false*/
//
// Vector math library
//
// Must be 'any' until Array constructor is understood to return an
// Array (not just any[]).
var VMathArrayConstructor = Array;

if ((typeof Float32Array !== "undefined") && (Float32Array.prototype !== undefined) && (Float32Array.prototype.slice === undefined)) {
    Float32Array.prototype.slice = function Float32ArraySlice(s, e) {
        var length = this.length;
        if (s === undefined) {
            s = 0;
        } else if (s < 0) {
            s += length;
        }
        if (e === undefined) {
            e = length;
        } else if (e < 0) {
            e += length;
        }

        length = (e - s);
        if (0 < length) {
            var dst = new Float32Array(length);
            var n = 0;
            do {
                dst[n] = this[s];
                n += 1;
                s += 1;
            } while(s < e);
            return dst;
        } else {
            return new Float32Array(0);
        }
    };
}

var VMath = {
    version: 1,
    // Default precision for equality comparisons
    precision: 1e-6,
    FLOAT_MAX: 3.402823466e+38,
    select: function selectFn(m, a, b) {
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isNumber(b)); */

        if (m) {
            return a;
        } else {
            return b;
        }
    },
    reciprocal: function reciprocalFn(a) {
        /* debug.assert(debug.isNumber(a)); */

        if (a !== 0.0) {
            return (1.0 / a);
        } else {
            throw "Division by zero";
        }
    },
    /* tslint:disable:no-bitwise */
    truncate: function truncateFn(value) {
        return (value | 0);
    },
    /* tslint:enable:no-bitwise */
    //
    // Vector2
    //
    v2BuildZero: function v2BuildZeroFn(dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = 0.0;
        dst[1] = 0.0;
        return dst;
    },
    v2BuildOne: function v2BuildOneFn(dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = 1.0;
        dst[1] = 1.0;
        return dst;
    },
    v2BuildXAxis: function v2BuildXAxisFn(dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = 1.0;
        dst[1] = 0.0;
        return dst;
    },
    v2BuildYAxis: function v2BuildYAxisFn(dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = 0.0;
        dst[1] = 1.0;
        return dst;
    },
    v2Build: function v2Fn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = a;
        dst[1] = b;
        return dst;
    },
    v2Copy: function v2CopyFn(src, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(2 === src.length); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = src[0];
        dst[1] = src[1];
        return dst;
    },
    v2Set: function v2SetFn(v, a) {
        /* debug.assert(debug.isVec2(v)); */
        v[0] = a[0];
        v[1] = a[1];
    },
    v2Neg: function v2NegFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = -a[0];
        dst[1] = -a[1];
        return dst;
    },
    v2Add: function v2AddFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = a[0] + b[0];
        dst[1] = a[1] + b[1];
        return dst;
    },
    v2Add3: function v2Add3Fn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isVec2(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */
        dst[0] = a[0] + b[0] + c[0];
        dst[1] = a[1] + b[1] + c[1];
        return dst;
    },
    v2Add4: function v2Add4Fn(a, b, c, d, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isVec2(c)); */
        /* debug.assert(debug.isVec2(d)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */

        dst[0] = a[0] + b[0] + c[0] + d[0];
        dst[1] = a[1] + b[1] + c[1] + d[1];
        return dst;
    },
    v2Sub: function v2SubFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */

        dst[0] = (a[0] - b[0]);
        dst[1] = (a[1] - b[1]);
        return dst;
    },
    v2Mul: function v2MulFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */

        dst[0] = (a[0] * b[0]);
        dst[1] = (a[1] * b[1]);
        return dst;
    },
    v2MulAdd: function v2MulAddFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        /* debug.assert(debug.isVec2(a)); */
        /* debug.assert(debug.isVec2(b)); */
        /* debug.assert(debug.isVec2(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec2(dst)); */

        dst[0] = (a[0] * b[0]) + c[0];
        dst[1] = (a[1] * b[1]) + c[1];
        return dst;
    },
    v2Dot: function v2DotFn(a, b) {
        return ((a[0] * b[0]) + (a[1] * b[1]));
    },
    v2PerpDot: function v2PerpDot(a, b) {
        return ((a[0] * b[1]) - (a[1] * b[0]));
    },
    v2LengthSq: function v2LengthSqFn(a) {
        var a0 = a[0];
        var a1 = a[1];
        return ((a0 * a0) + (a1 * a1));
    },
    v2Length: function v2LengthFn(a) {
        var a0 = a[0];
        var a1 = a[1];
        return Math.sqrt((a0 * a0) + (a1 * a1));
    },
    v2Reciprocal: function v2ReciprocalFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        var rcp = VMath.reciprocal;
        dst[0] = rcp(a[0]);
        dst[1] = rcp(a[1]);
        return dst;
    },
    v2Normalize: function v2NormalizeFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        var a0 = a[0];
        var a1 = a[1];
        var lsq = ((a0 * a0) + (a1 * a1));
        if (lsq > 0.0) {
            var lr = 1.0 / Math.sqrt(lsq);
            dst[0] = (a0 * lr);
            dst[1] = (a1 * lr);
        } else {
            dst[0] = 0;
            dst[1] = 0;
        }
        return dst;
    },
    v2Abs: function v2AbsFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        var abs = Math.abs;
        dst[0] = abs(a[0]);
        dst[1] = abs(a[1]);
        return dst;
    },
    v2Max: function v2MaxFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        var max = Math.max;
        dst[0] = max(a[0], b[0]);
        dst[1] = max(a[1], b[1]);
        return dst;
    },
    v2Min: function v2MinFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        var min = Math.min;
        dst[0] = min(a[0], b[0]);
        dst[1] = min(a[1], b[1]);
        return dst;
    },
    v2Equal: function v2EqualFn(a, b, precision) {
        var abs = Math.abs;
        if (precision === undefined) {
            precision = this.precision;
        }
        return (abs(a[0] - b[0]) <= precision && abs(a[1] - b[1]) <= precision);
    },
    // Vector2 'masks'
    v2MaskEqual: function v2MaskEqualFn(a, b) {
        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b[0]) <= precision),
            (abs(a[1] - b[1]) <= precision)
        ];
    },
    v2MaskLess: function v2MaskLessFn(a, b) {
        return [
            (a[0] < b[0]),
            (a[1] < b[1])
        ];
    },
    v2MaskGreater: function v2MaskGreaterFn(a, b) {
        return [
            (a[0] > b[0]),
            (a[1] > b[1])
        ];
    },
    v2MaskGreaterEq: function v2MaskGreaterEqFn(a, b) {
        return [
            (a[0] >= b[0]),
            (a[1] >= b[1])
        ];
    },
    v2MaskNot: function v2MaskNotFn(a) {
        return [
            !a[0],
            !a[1]
        ];
    },
    v2MaskOr: function v2MaskOrFn(a, b) {
        return [
            (a[0] || b[0]),
            (a[1] || b[1])
        ];
    },
    v2MaskAnd: function v2MaskAndFn(a, b) {
        return [
            (a[0] && b[0]),
            (a[1] && b[1])
        ];
    },
    v2Select: function v2SelectFn(m, a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        dst[0] = m[0] ? a[0] : b[0];
        dst[1] = m[1] ? a[1] : b[1];
        return dst;
    },
    // Vector2 operations with scalar
    v2ScalarBuild: function v2ScalarBuildFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        dst[0] = a;
        dst[1] = a;

        return dst;
    },
    v2ScalarMax: function v2ScalarMaxFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        var max = Math.max;
        dst[0] = max(a[0], b);
        dst[1] = max(a[1], b);

        return dst;
    },
    v2ScalarMin: function v2ScalarMinFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        var min = Math.min;
        dst[0] = min(a[0], b);
        dst[1] = min(a[1], b);

        return dst;
    },
    v2ScalarAdd: function v2ScalarAddFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        dst[0] = (a[0] + b);
        dst[1] = (a[1] + b);

        return dst;
    },
    v2ScalarSub: function v2ScalarSubFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }

        dst[0] = (a[0] - b);
        dst[1] = (a[1] - b);

        return dst;
    },
    v2ScalarMul: function v2ScalarMulFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        if (b === 0) {
            dst[0] = 0;
            dst[1] = 0;
        } else {
            dst[0] = a[0] * b;
            dst[1] = a[1] * b;
        }
        return dst;
    },
    v2AddScalarMul: function v2AddScalarMulFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        dst[0] = a[0] + b[0] * c;
        dst[1] = a[1] + b[1] * c;
        return dst;
    },
    // Vector2 'masks' with scalars
    v2EqualScalarMask: function v2EqualScalarMaskFn(a, b) {
        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b) <= precision),
            (abs(a[1] - b) <= precision)
        ];
    },
    v2LessScalarMask: function v2LessScalarMaskFn(a, b) {
        return [
            (a[0] < b),
            (a[1] < b)
        ];
    },
    v2GreaterScalarMask: function v2GreaterScalarMaskFn(a, b) {
        return [
            (a[0] > b),
            (a[1] > b)
        ];
    },
    v2GreaterEqScalarMask: function v2GreaterEqScalarMaskFn(a, b) {
        return [
            (a[0] >= b),
            (a[1] >= b)
        ];
    },
    v2Lerp: function v2LerpFn(a, b, t, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(2);
        }
        dst[0] = (a[0] + ((b[0] - a[0]) * t));
        dst[1] = (a[1] + ((b[1] - a[1]) * t));
        return dst;
    },
    //
    // Vector3
    //
    v3BuildZero: function v3BuildZeroFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = 0.0;
        res[1] = 0.0;
        res[2] = 0.0;
        return res;
    },
    v3BuildOne: function v3BuildOneFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = 1.0;
        res[1] = 1.0;
        res[2] = 1.0;
        return res;
    },
    v3BuildXAxis: function v3BuildXAxisFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = 1.0;
        res[1] = 0.0;
        res[2] = 0.0;
        return res;
    },
    v3BuildYAxis: function v3BuildYAxisFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = 0.0;
        res[1] = 1.0;
        res[2] = 0.0;
        return res;
    },
    v3BuildZAxis: function v3BuildZAxisFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = 0.0;
        res[1] = 0.0;
        res[2] = 1.0;
        return res;
    },
    v3Build: function v3Fn(a, b, c, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isNumber(c)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = a;
        res[1] = b;
        res[2] = c;
        return res;
    },
    v3Copy: function v3CopyFn(src, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = src[0];
        res[1] = src[1];
        res[2] = src[2];
        return res;
    },
    v3Set: function v3SetFn(v, a) {
        /* debug.assert(debug.isVec3(v)); */
        v[0] = a[0];
        v[1] = a[1];
        v[2] = a[2];
    },
    v3Neg: function v3NegFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */
        dst[0] = -a[0];
        dst[1] = -a[1];
        dst[2] = -a[2];
        return dst;
    },
    v3Add: function v3AddFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = (a[0] + b[0]);
        res[1] = (a[1] + b[1]);
        res[2] = (a[2] + b[2]);
        return res;
    },
    v3Add3: function v3Add3Fn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isVec3(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */
        dst[0] = a[0] + b[0] + c[0];
        dst[1] = a[1] + b[1] + c[1];
        dst[2] = a[2] + b[2] + c[2];
        return dst;
    },
    v3Add4: function v3Add4Fn(a, b, c, d, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isVec3(c)); */
        /* debug.assert(debug.isVec3(d)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */
        dst[0] = a[0] + b[0] + c[0] + d[0];
        dst[1] = a[1] + b[1] + c[1] + d[1];
        dst[2] = a[2] + b[2] + c[2] + d[2];
        return dst;
    },
    v3Sub: function v3SubFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        res[0] = (a[0] - b[0]);
        res[1] = (a[1] - b[1]);
        res[2] = (a[2] - b[2]);
        return res;
    },
    v3Mul: function v3MulFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */
        dst[0] = (a[0] * b[0]);
        dst[1] = (a[1] * b[1]);
        dst[2] = (a[2] * b[2]);
        return dst;
    },
    v3MulAdd: function v3MulAddFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isVec3(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */
        dst[0] = (a[0] * b[0]) + c[0];
        dst[1] = (a[1] * b[1]) + c[1];
        dst[2] = (a[2] * b[2]) + c[2];
        return dst;
    },
    v3Dot: function v3DotFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        return ((a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]));
    },
    v3Cross: function v3CrossFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        res[0] = ((a1 * b2) - (a2 * b1));
        res[1] = ((a2 * b0) - (a0 * b2));
        res[2] = ((a0 * b1) - (a1 * b0));
        return res;
    },
    v3LengthSq: function v3LengthSqFn(a) {
        /* debug.assert(debug.isVec3(a)); */
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        return ((a0 * a0) + (a1 * a1) + (a2 * a2));
    },
    v3Length: function v3LengthFn(a) {
        /* debug.assert(debug.isVec3(a)); */
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        return Math.sqrt((a0 * a0) + (a1 * a1) + (a2 * a2));
    },
    v3Reciprocal: function v3ReciprocalFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        var rcp = VMath.reciprocal;
        dst[0] = rcp(a[0]);
        dst[1] = rcp(a[1]);
        dst[2] = rcp(a[2]);
        return dst;
    },
    v3Normalize: function v3NormalizeFn(a, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var lsq = ((a0 * a0) + (a1 * a1) + (a2 * a2));
        if (lsq > 0.0) {
            var lr = 1.0 / Math.sqrt(lsq);
            res[0] = (a0 * lr);
            res[1] = (a1 * lr);
            res[2] = (a2 * lr);
        } else {
            res[0] = 0;
            res[1] = 0;
            res[2] = 0;
        }
        return res;
    },
    v3Abs: function v3AbsFn(a, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var abs = Math.abs;
        res[0] = abs(a[0]);
        res[1] = abs(a[1]);
        res[2] = abs(a[2]);
        return res;
    },
    v3Max: function v3MaxFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var max = Math.max;
        res[0] = max(a[0], b[0]);
        res[1] = max(a[1], b[1]);
        res[2] = max(a[2], b[2]);
        return res;
    },
    v3Min: function v3MinFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var min = Math.min;
        res[0] = min(a[0], b[0]);
        res[1] = min(a[1], b[1]);
        res[2] = min(a[2], b[2]);
        return res;
    },
    v3Equal: function v3EqualFn(a, b, precision) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        var abs = Math.abs;
        if (precision === undefined) {
            precision = this.precision;
        }
        return (abs(a[0] - b[0]) <= precision && abs(a[1] - b[1]) <= precision && abs(a[2] - b[2]) <= precision);
    },
    // Vector3 'masks'
    v3MaskEqual: function v3MaskEqualFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b[0]) <= precision),
            (abs(a[1] - b[1]) <= precision),
            (abs(a[2] - b[2]) <= precision)
        ];
    },
    v3MaskLess: function v3MaskLessFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        return [
            (a[0] < b[0]),
            (a[1] < b[1]),
            (a[2] < b[2])
        ];
    },
    v3MaskGreater: function v3MaskGreaterFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        return [
            (a[0] > b[0]),
            (a[1] > b[1]),
            (a[2] > b[2])
        ];
    },
    v3MaskGreaterEq: function v3MaskGreaterEqFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        return [
            (a[0] >= b[0]),
            (a[1] >= b[1]),
            (a[2] >= b[2])
        ];
    },
    v3MaskNot: function v3MaskNotFn(a) {
        /* debug.assert(debug.isVec3(a)); */

        return [
            !a[0],
            !a[1],
            !a[2]
        ];
    },
    v3MaskOr: function v3MaskOrFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        return [
            (a[0] || b[0]),
            (a[1] || b[1]),
            (a[2] || b[2])
        ];
    },
    v3MaskAnd: function v3MaskAndFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */

        return [
            (a[0] && b[0]),
            (a[1] && b[1]),
            (a[2] && b[2])
        ];
    },
    v3Select: function v3SelectFn(m, a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = m[0] ? a[0] : b[0];
        dst[1] = m[1] ? a[1] : b[1];
        dst[2] = m[2] ? a[2] : b[2];
        return dst;
    },
    // Vector3 operations with scalar
    v3ScalarBuild: function v3ScalarBuildFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = a;
        dst[1] = a;
        dst[2] = a;

        return dst;
    },
    v3ScalarMax: function v3ScalarMaxFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        var max = Math.max;
        dst[0] = max(a[0], b);
        dst[1] = max(a[1], b);
        dst[2] = max(a[2], b);

        return dst;
    },
    v3ScalarMin: function v3ScalarMinFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        var min = Math.min;
        dst[0] = min(a[0], b);
        dst[1] = min(a[1], b);
        dst[2] = min(a[2], b);

        return dst;
    },
    v3ScalarAdd: function v3ScalarAddFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = (a[0] + b);
        dst[1] = (a[1] + b);
        dst[2] = (a[2] + b);

        return dst;
    },
    v3ScalarSub: function v3ScalarSubFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = (a[0] - b);
        dst[1] = (a[1] - b);
        dst[2] = (a[2] - b);

        return dst;
    },
    v3ScalarMul: function v3ScalarMulFn(a, b, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        if (b === 0) {
            res[0] = 0;
            res[1] = 0;
            res[2] = 0;
        } else {
            res[0] = (a[0] * b);
            res[1] = (a[1] * b);
            res[2] = (a[2] * b);
        }
        return res;
    },
    v3AddScalarMul: function v3AddScalarMulFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isNumber(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = a[0] + b[0] * c;
        dst[1] = a[1] + b[1] * c;
        dst[2] = a[2] + b[2] * c;

        return dst;
    },
    // Vector3 'masks' with scalars
    v3EqualScalarMask: function v3EqualScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */

        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b) <= precision),
            (abs(a[1] - b) <= precision),
            (abs(a[2] - b) <= precision)
        ];
    },
    v3LessScalarMask: function v3LessScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] < b),
            (a[1] < b),
            (a[2] < b)
        ];
    },
    v3GreaterScalarMask: function v3GreaterScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] > b),
            (a[1] > b),
            (a[2] > b)
        ];
    },
    v3GreaterEqScalarMask: function v3GreaterEqScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] >= b),
            (a[1] >= b),
            (a[2] >= b)
        ];
    },
    v3Lerp: function v3LerpFn(a, b, t, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isVec3(a)); */
        /* debug.assert(debug.isVec3(b)); */
        /* debug.assert(debug.isNumber(t)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = (a[0] + ((b[0] - a[0]) * t));
        res[1] = (a[1] + ((b[1] - a[1]) * t));
        res[2] = (a[2] + ((b[2] - a[2]) * t));

        return res;
    },
    //
    // Vector4
    //
    v4BuildZero: function v4BuildZeroFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec4(res)); */

        res[0] = 0.0;
        res[1] = 0.0;
        res[2] = 0.0;
        res[3] = 0.0;
        return res;
    },
    v4BuildOne: function v4BuildOneFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec4(res)); */

        res[0] = 1.0;
        res[1] = 1.0;
        res[2] = 1.0;
        res[3] = 1.0;
        return res;
    },
    v4Build: function v4BuildFn(a, b, c, d, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isNumber(c)); */
        /* debug.assert(debug.isNumber(d)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec4(res)); */

        res[0] = a;
        res[1] = b;
        res[2] = c;
        res[3] = d;
        return res;
    },
    v4Copy: function v4CopyFn(src, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isMathType(res) && debug.isVec4(res)); */

        res[0] = src[0];
        res[1] = src[1];
        res[2] = src[2];
        res[3] = src[3];
        return res;
    },
    v4Set: function v4SetFn(v, a) {
        /* debug.assert(debug.isVec4(v)); */
        v[0] = a[0];
        v[1] = a[1];
        v[2] = a[2];
        v[3] = a[3];
    },
    v4Neg: function v4NegFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = -a[0];
        dst[1] = -a[1];
        dst[2] = -a[2];
        dst[3] = -a[3];

        return dst;
    },
    v4Add: function v4AddFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] + b[0]);
        dst[1] = (a[1] + b[1]);
        dst[2] = (a[2] + b[2]);
        dst[3] = (a[3] + b[3]);
        return dst;
    },
    v4Add3: function v4Add3Fn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isVec4(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = a[0] + b[0] + c[0];
        dst[1] = a[1] + b[1] + c[1];
        dst[2] = a[2] + b[2] + c[2];
        dst[3] = a[3] + b[3] + c[3];

        return dst;
    },
    v4Add4: function v4Add4Fn(a, b, c, d, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isVec4(c)); */
        /* debug.assert(debug.isVec4(d)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = a[0] + b[0] + c[0] + d[0];
        dst[1] = a[1] + b[1] + c[1] + d[1];
        dst[2] = a[2] + b[2] + c[2] + d[2];
        dst[3] = a[3] + b[3] + c[3] + d[3];

        return dst;
    },
    v4Sub: function v4SubFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] - b[0]);
        dst[1] = (a[1] - b[1]);
        dst[2] = (a[2] - b[2]);
        dst[3] = (a[3] - b[3]);
        return dst;
    },
    v4Mul: function v4MulFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] * b[0]);
        dst[1] = (a[1] * b[1]);
        dst[2] = (a[2] * b[2]);
        dst[3] = (a[3] * b[3]);
        return dst;
    },
    v4MulAdd: function v4MulAddFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isVec4(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] * b[0]) + c[0];
        dst[1] = (a[1] * b[1]) + c[1];
        dst[2] = (a[2] * b[2]) + c[2];
        dst[3] = (a[3] * b[3]) + c[3];

        return dst;
    },
    v4Dot: function v4DotFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return ((a[0] * b[0]) + (a[1] * b[1]) + (a[2] * b[2]) + (a[3] * b[3]));
    },
    v4LengthSq: function v4LengthSqFn(a) {
        /* debug.assert(debug.isVec4(a)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        return ((a0 * a0) + (a1 * a1) + (a2 * a2) + (a3 * a3));
    },
    v4Length: function v4LengthFn(a) {
        /* debug.assert(debug.isVec4(a)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        return Math.sqrt((a0 * a0) + (a1 * a1) + (a2 * a2) + (a3 * a3));
    },
    v4Reciprocal: function v4ReciprocalFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var rcp = VMath.reciprocal;
        dst[0] = rcp(a[0]);
        dst[1] = rcp(a[1]);
        dst[2] = rcp(a[2]);
        dst[3] = rcp(a[3]);

        return dst;
    },
    v4Normalize: function v4NormalizeFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];

        var lsq = ((a0 * a0) + (a1 * a1) + (a2 * a2) + (a3 * a3));
        if (lsq > 0.0) {
            var lr = 1.0 / Math.sqrt(lsq);
            dst[0] = a0 * lr;
            dst[1] = a1 * lr;
            dst[2] = a2 * lr;
            dst[3] = a3 * lr;
        } else {
            dst[0] = 0;
            dst[1] = 0;
            dst[2] = 0;
            dst[3] = 0;
        }
        return dst;
    },
    v4Abs: function v4AbsFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var abs = Math.abs;
        dst[0] = abs(a[0]);
        dst[1] = abs(a[1]);
        dst[2] = abs(a[2]);
        dst[3] = abs(a[3]);

        return dst;
    },
    v4Max: function v4MaxFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var max = Math.max;
        dst[0] = max(a[0], b[0]);
        dst[1] = max(a[1], b[1]);
        dst[2] = max(a[2], b[2]);
        dst[3] = max(a[3], b[3]);

        return dst;
    },
    v4Min: function v4MinFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var min = Math.min;
        dst[0] = min(a[0], b[0]);
        dst[1] = min(a[1], b[1]);
        dst[2] = min(a[2], b[2]);
        dst[3] = min(a[3], b[3]);

        return dst;
    },
    v4Equal: function v4EqualFn(a, b, precision) {
        var abs = Math.abs;
        if (precision === undefined) {
            precision = this.precision;
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isNumber(precision)); */

        return (abs(a[0] - b[0]) <= precision && abs(a[1] - b[1]) <= precision && abs(a[2] - b[2]) <= precision && abs(a[3] - b[3]) <= precision);
    },
    // Vector3 'masks'
    v4MaskEqual: function v4MaskEqualFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b[0]) <= precision),
            (abs(a[1] - b[1]) <= precision),
            (abs(a[2] - b[2]) <= precision),
            (abs(a[3] - b[3]) <= precision)
        ];
    },
    v4MaskLess: function v4MaskLessFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return [
            (a[0] < b[0]),
            (a[1] < b[1]),
            (a[2] < b[2]),
            (a[3] < b[3])
        ];
    },
    v4MaskGreater: function v4MaskGreaterFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return [
            (a[0] > b[0]),
            (a[1] > b[1]),
            (a[2] > b[2]),
            (a[3] > b[3])
        ];
    },
    v4MaskGreaterEq: function v4MaskGreaterEqFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return [
            (a[0] >= b[0]),
            (a[1] >= b[1]),
            (a[2] >= b[2]),
            (a[3] >= b[3])
        ];
    },
    v4MaskNot: function v4MaskNotFn(a) {
        /* debug.assert(debug.isVec4(a)); */

        return [
            !a[0],
            !a[1],
            !a[2],
            !a[3]
        ];
    },
    v4MaskOr: function v4MaskOrFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return [
            (a[0] || b[0]),
            (a[1] || b[1]),
            (a[2] || b[2]),
            (a[3] || b[3])
        ];
    },
    v4MaskAnd: function v4MaskAndFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */

        return [
            (a[0] && b[0]),
            (a[1] && b[1]),
            (a[2] && b[2]),
            (a[3] && b[3])
        ];
    },
    v4Many: function v4ManyFn(m) {
        return (m[0] || m[1] || m[2] || m[3]);
    },
    v4MaskAll: function v4MaskAllFn(m) {
        return (m[0] && m[1] && m[2] && m[3]);
    },
    v4Select: function v4SelectFn(m, a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = m[0] ? a[0] : b[0];
        dst[1] = m[1] ? a[1] : b[1];
        dst[2] = m[2] ? a[2] : b[2];
        dst[3] = m[3] ? a[3] : b[3];

        return dst;
    },
    // Vector4 operations with scalar
    v4ScalarBuild: function v4ScalarBuildFn(a, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isNumber(a)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = a;
        dst[1] = a;
        dst[2] = a;
        dst[3] = a;

        return dst;
    },
    v4ScalarMax: function v4ScalarMaxFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var max = Math.max;
        dst[0] = max(a[0], b);
        dst[1] = max(a[1], b);
        dst[2] = max(a[2], b);
        dst[3] = max(a[3], b);

        return dst;
    },
    v4ScalarMin: function v4ScalarMinFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var min = Math.min;
        dst[0] = min(a[0], b);
        dst[1] = min(a[1], b);
        dst[2] = min(a[2], b);
        dst[3] = min(a[3], b);

        return dst;
    },
    v4ScalarAdd: function v4ScalarAddFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] + b);
        dst[1] = (a[1] + b);
        dst[2] = (a[2] + b);
        dst[3] = (a[3] + b);

        return dst;
    },
    v4ScalarSub: function v4ScalarSubFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] - b);
        dst[1] = (a[1] - b);
        dst[2] = (a[2] - b);
        dst[3] = (a[3] - b);

        return dst;
    },
    v4ScalarMul: function v4ScalarMulFn(a, b, dst) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        if (b === 0) {
            return VMath.v4BuildZero(dst);
        } else {
            if (dst === undefined) {
                dst = new VMathArrayConstructor(4);
            }
            /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

            dst[0] = (a[0] * b);
            dst[1] = (a[1] * b);
            dst[2] = (a[2] * b);
            dst[3] = (a[3] * b);

            return dst;
        }
    },
    v4AddScalarMul: function v4AddScalarMulFn(a, b, c, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isNumber(c)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = a[0] + b[0] * c;
        dst[1] = a[1] + b[1] * c;
        dst[2] = a[2] + b[2] * c;
        dst[3] = a[3] + b[3] * c;

        return dst;
    },
    v4ScalarEqual: function v4ScalarEqualFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        var abs = Math.abs;
        var precision = VMath.precision;
        return (abs(a[0] - b) <= precision && abs(a[1] - b) <= precision && abs(a[2] - b) <= precision && abs(a[3] - b) <= precision);
    },
    // Vector3 'masks' with scalars
    v4EqualScalarMask: function v4EqualScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        var abs = Math.abs;
        var precision = VMath.precision;
        return [
            (abs(a[0] - b) <= precision),
            (abs(a[1] - b) <= precision),
            (abs(a[2] - b) <= precision),
            (abs(a[3] - b) <= precision)
        ];
    },
    v4LessScalarMask: function v4LessScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] < b),
            (a[1] < b),
            (a[2] < b),
            (a[3] < b)
        ];
    },
    v4GreaterScalarMask: function v4GreaterScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] > b),
            (a[1] > b),
            (a[2] > b),
            (a[3] > b)
        ];
    },
    v4GreaterEqScalarMask: function v4GreaterEqScalarMaskFn(a, b) {
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isNumber(b)); */

        return [
            (a[0] >= b),
            (a[1] >= b),
            (a[2] >= b),
            (a[3] >= b)
        ];
    },
    v4Lerp: function v4LerpFn(a, b, t, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec4(a)); */
        /* debug.assert(debug.isVec4(b)); */
        /* debug.assert(debug.isNumber(t)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = (a[0] + ((b[0] - a[0]) * t));
        dst[1] = (a[1] + ((b[1] - a[1]) * t));
        dst[2] = (a[2] + ((b[2] - a[2]) * t));
        dst[3] = (a[3] + ((b[3] - a[3]) * t));
        return dst;
    },
    //
    // AABB
    //
    aabbBuild: function aabbBuildFn(a0, a1, a2, a3, a4, a5, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isNumber(a0)); */
        /* debug.assert(debug.isNumber(a1)); */
        /* debug.assert(debug.isNumber(a2)); */
        /* debug.assert(debug.isNumber(a3)); */
        /* debug.assert(debug.isNumber(a4)); */
        /* debug.assert(debug.isNumber(a5)); */
        /* debug.assert(debug.isMathType(res) && debug.isAABB(res)); */

        res[0] = a0;
        res[1] = a1;
        res[2] = a2;
        res[3] = a3;
        res[4] = a4;
        res[5] = a5;

        return res;
    },
    aabbBuildEmpty: function aabbBuildEmptyFn(dst) {
        var float_max = this.FLOAT_MAX;

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isMathType(res) && debug.isAABB(res)); */

        res[0] = float_max;
        res[1] = float_max;
        res[2] = float_max;
        res[3] = -float_max;
        res[4] = -float_max;
        res[5] = -float_max;

        return res;
    },
    aabbCopy: function aabbCopyFn(aabb, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isMathType(res) && debug.isAABB(res)); */

        res[0] = aabb[0];
        res[1] = aabb[1];
        res[2] = aabb[2];
        res[3] = aabb[3];
        res[4] = aabb[4];
        res[5] = aabb[5];

        return res;
    },
    aabbSet: function aabbSet(dst, src) {
        /* debug.assert(debug.isMathType(dst) && debug.isAABB(dst)); */

        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
        dst[4] = src[4];
        dst[5] = src[5];
    },
    aabbIsEmpty: function aabbIsEmptyFn(aabb) {
        return aabb[0] > aabb[3];
    },
    aabbMin: function aabbMinFn(aabb, dst) {
        /* debug.assert(debug.isAABB(aabb)); */
        if (dst === undefined) {
            return aabb.slice(0, 3);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = aabb[0];
        dst[1] = aabb[1];
        dst[2] = aabb[2];
        return dst;
    },
    aabbMax: function aabbMaxFn(aabb, dst) {
        /* debug.assert(debug.isAABB(aabb)); */
        if (dst === undefined) {
            return aabb.slice(3, 6);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = aabb[3];
        dst[1] = aabb[4];
        dst[2] = aabb[5];
        return dst;
    },
    aabbGetCenterAndHalf: function aabbGetCenterAndHalfFn(aabb, center, half) {
        /* debug.assert(debug.isAABB(aabb)); */
        /* debug.assert(debug.isVec3(center)); */
        /* debug.assert(debug.isVec3(half)); */

        var cX = (aabb[0] + aabb[3]) * 0.5;
        var cY = (aabb[1] + aabb[4]) * 0.5;
        var cZ = (aabb[2] + aabb[5]) * 0.5;

        center[0] = cX;
        center[1] = cY;
        center[2] = cZ;

        half[0] = aabb[3] - cX;
        half[1] = aabb[4] - cY;
        half[2] = aabb[5] - cZ;
    },
    aabbIsInsidePlanes: function aabbIsInsidePlanesFn(aabb, planes) {
        /* debug.assert(debug.isAABB(aabb)); */
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            var distance = (d0 * (d0 < 0 ? aabb[0] : aabb[3]) + d1 * (d1 < 0 ? aabb[1] : aabb[4]) + d2 * (d2 < 0 ? aabb[2] : aabb[5]));
            if (distance < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    },
    aabbIsFullyInsidePlanes: function aabbIsFullyInsidePlanesFn(aabb, planes) {
        /* debug.assert(debug.isAABB(aabb)); */
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            var distance = (d0 * (d0 > 0 ? aabb[0] : aabb[3]) + d1 * (d1 > 0 ? aabb[1] : aabb[4]) + d2 * (d2 > 0 ? aabb[2] : aabb[5]));
            if (distance < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    },
    aabbUnion: function aabbUnionFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isAABB(a)); */
        /* debug.assert(debug.isAABB(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isAABB(dst)); */

        dst[0] = a[0] < b[0] ? a[0] : b[0];
        dst[1] = a[1] < b[1] ? a[1] : b[1];
        dst[2] = a[2] < b[2] ? a[2] : b[2];
        dst[3] = a[3] > b[3] ? a[3] : b[3];
        dst[4] = a[4] > b[4] ? a[4] : b[4];
        dst[5] = a[5] > b[5] ? a[5] : b[5];

        return dst;
    },
    aabbUnionArray: function aabbUnionArrayFn(aabbArray, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isAABB(dst)); */
        /* debug.assert(aabbArray.length > 0); */

        VMath.aabbCopy(aabbArray[0], dst);

        var numAABBs = aabbArray.length;
        for (var i = 1; i < numAABBs; i += 1) {
            var aabb = aabbArray[i];
            /* debug.assert(debug.isAABB(aabb)); */

            dst[0] = (dst[0] < aabb[0] ? dst[0] : aabb[0]);
            dst[1] = (dst[1] < aabb[1] ? dst[1] : aabb[1]);
            dst[2] = (dst[2] < aabb[2] ? dst[2] : aabb[2]);
            dst[3] = (dst[3] > aabb[3] ? dst[3] : aabb[3]);
            dst[4] = (dst[4] > aabb[4] ? dst[4] : aabb[4]);
            dst[5] = (dst[5] > aabb[5] ? dst[5] : aabb[5]);
        }

        return dst;
    },
    aabbAddPoints: function aabbAddPointFn(aabb, ps) {
        /* debug.assert(debug.isAABB(aabb)); */
        var i;
        var numPoints = ps.length;

        var r0 = aabb[0];
        var r1 = aabb[1];
        var r2 = aabb[2];
        var r3 = aabb[3];
        var r4 = aabb[4];
        var r5 = aabb[5];

        var p, p0, p1, p2;

        for (i = 0; i < numPoints; i += 1) {
            p = ps[i];
            /* debug.assert(3 === p.length); */
            p0 = p[0];
            p1 = p[1];
            p2 = p[2];

            r0 = (r0 < p0 ? r0 : p0);
            r1 = (r1 < p1 ? r1 : p1);
            r2 = (r2 < p2 ? r2 : p2);
            r3 = (r3 > p0 ? r3 : p0);
            r4 = (r4 > p1 ? r4 : p1);
            r5 = (r5 > p2 ? r5 : p2);
        }

        aabb[0] = r0;
        aabb[1] = r1;
        aabb[2] = r2;
        aabb[3] = r3;
        aabb[4] = r4;
        aabb[5] = r5;
    },
    aabbTransform: function aabbTransformFn(aabb, matrix, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isAABB(aabb)); */
        /* debug.assert(debug.isMtx43(matrix)); */
        /* debug.assert(debug.isMathType(dst) && debug.isAABB(dst)); */

        var cX = (aabb[0] + aabb[3]) * 0.5;
        var cY = (aabb[1] + aabb[4]) * 0.5;
        var cZ = (aabb[2] + aabb[5]) * 0.5;

        var hX = aabb[3] - cX;
        var hY = aabb[4] - cY;
        var hZ = aabb[5] - cZ;

        var m0 = matrix[0];
        var m1 = matrix[1];
        var m2 = matrix[2];
        var m3 = matrix[3];
        var m4 = matrix[4];
        var m5 = matrix[5];
        var m6 = matrix[6];
        var m7 = matrix[7];
        var m8 = matrix[8];

        var ctX = matrix[9] + (m0 * cX + m3 * cY + m6 * cZ);
        var ctY = matrix[10] + (m1 * cX + m4 * cY + m7 * cZ);
        var ctZ = matrix[11] + (m2 * cX + m5 * cY + m8 * cZ);

        var abs = Math.abs;

        var htX = (abs(m0) * hX + abs(m3) * hY + abs(m6) * hZ);
        var htY = (abs(m1) * hX + abs(m4) * hY + abs(m7) * hZ);
        var htZ = (abs(m2) * hX + abs(m5) * hY + abs(m8) * hZ);

        dst[0] = ctX - htX;
        dst[1] = ctY - htY;
        dst[2] = ctZ - htZ;
        dst[3] = ctX + htX;
        dst[4] = ctY + htY;
        dst[5] = ctZ + htZ;

        return dst;
    },
    aabbIntercept: function aabbInterceptFn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(6);
        }
        /* debug.assert(debug.isAABB(a)); */
        /* debug.assert(debug.isAABB(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isAABB(dst)); */

        dst[0] = a[0] > b[0] ? a[0] : b[0];
        dst[1] = a[1] > b[1] ? a[1] : b[1];
        dst[2] = a[2] > b[2] ? a[2] : b[2];
        dst[3] = a[3] < b[3] ? a[3] : b[3];
        dst[4] = a[4] < b[4] ? a[4] : b[4];
        dst[5] = a[5] < b[5] ? a[5] : b[5];

        return dst;
    },
    aabbOverlaps: function aabbOverlapsFn(a, b) {
        /* debug.assert(debug.isAABB(a)); */
        /* debug.assert(debug.isAABB(b)); */

        return ((a[0] <= b[3]) && (a[1] <= b[4]) && (a[2] <= b[5]) && (a[3] >= b[0]) && (a[4] >= b[1]) && (a[5] >= b[2]));
    },
    aabbSphereOverlaps: function aabbSphereOverlapsFn(aabb, center, radius) {
        /* debug.assert(debug.isAABB(aabb)); */
        /* debug.assert(debug.isVec3(center)); */
        /* debug.assert(debug.isNumber(radius)); */

        var centerX = center[0];
        var centerY = center[1];
        var centerZ = center[2];
        var radiusSquared = radius * radius;

        var minX = aabb[0];
        var minY = aabb[1];
        var minZ = aabb[2];
        var maxX = aabb[3];
        var maxY = aabb[4];
        var maxZ = aabb[5];
        var totalDistance = 0, sideDistance;

        if (centerX < minX) {
            sideDistance = (minX - centerX);
            totalDistance += (sideDistance * sideDistance);
        } else if (centerX > maxX) {
            sideDistance = (centerX - maxX);
            totalDistance += (sideDistance * sideDistance);
        }
        if (centerY < minY) {
            sideDistance = (minY - centerY);
            totalDistance += (sideDistance * sideDistance);
        } else if (centerY > maxY) {
            sideDistance = (centerY - maxY);
            totalDistance += (sideDistance * sideDistance);
        }
        if (centerZ < minZ) {
            sideDistance = (minZ - centerZ);
            totalDistance += (sideDistance * sideDistance);
        } else if (centerZ > maxZ) {
            sideDistance = (centerZ - maxZ);
            totalDistance += (sideDistance * sideDistance);
        }
        return (totalDistance <= radiusSquared);
    },
    aabbIsInside: function aabbIsInsideFn(a, b) {
        /* debug.assert(debug.isAABB(a)); */
        /* debug.assert(debug.isAABB(b)); */

        return ((a[0] >= b[0]) && (a[1] >= b[1]) && (a[2] >= b[2]) && (a[3] <= b[3]) && (a[4] <= b[4]) && (a[5] <= b[5]));
    },
    aabbTestInside: function aabbTestInsideFn(a, b) {
        /* debug.assert(debug.isAABB(a)); */
        /* debug.assert(debug.isAABB(b)); */

        if ((a[0] <= b[3]) && (a[1] <= b[4]) && (a[2] <= b[5]) && (a[3] >= b[0]) && (a[4] >= b[1]) && (a[5] >= b[2])) {
            if ((a[0] >= b[0]) && (a[1] >= b[1]) && (a[2] >= b[2]) && (a[3] <= b[3]) && (a[4] <= b[4]) && (a[5] <= b[5])) {
                return 2;
            }
            return 1;
        }

        return 0;
    },
    //
    // Matrix
    //
    m33BuildIdentity: function m33BuildIdentityFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */

        res[0] = 1.0;
        res[1] = 0.0;
        res[2] = 0.0;
        res[3] = 0.0;
        res[4] = 1.0;
        res[5] = 0.0;
        res[6] = 0.0;
        res[7] = 0.0;
        res[8] = 1.0;

        return res;
    },
    // Matrix33
    m33Build: function m33BuildFn(r, u, a, dst) {
        var res;
        var length = arguments.length;
        if (length >= 9) {
            if (length > 9) {
                res = arguments[9];
                if (res === undefined) {
                    res = new VMathArrayConstructor(9);
                }
                /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */
            } else {
                res = new VMathArrayConstructor(9);
            }

            res[0] = arguments[0];
            res[1] = arguments[1];
            res[2] = arguments[2];
            res[3] = arguments[3];
            res[4] = arguments[4];
            res[5] = arguments[5];
            res[6] = arguments[6];
            res[7] = arguments[7];
            res[8] = arguments[8];
        } else {
            res = dst;
            if (res === undefined) {
                res = new VMathArrayConstructor(9);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */

            res[0] = r[0];
            res[1] = r[1];
            res[2] = r[2];
            res[3] = u[0];
            res[4] = u[1];
            res[5] = u[2];
            res[6] = a[0];
            res[7] = a[1];
            res[8] = a[2];
        }

        return res;
    },
    m33Copy: function m33CopyFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */

        res[0] = m[0];
        res[1] = m[1];
        res[2] = m[2];
        res[3] = m[3];
        res[4] = m[4];
        res[5] = m[5];
        res[6] = m[6];
        res[7] = m[7];
        res[8] = m[8];

        return res;
    },
    m33FromAxisRotation: function m33FromAxisRotationFn(axis, angle, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isVec3(axis) || debug.isVec4(axis)); */
        /* debug.assert(debug.isNumber(angle)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */

        var s = Math.sin(angle);
        var c = Math.cos(angle);
        var t = 1.0 - c;
        var axisX = axis[0];
        var axisY = axis[1];
        var axisZ = axis[2];
        var tx = t * axisX;
        var ty = t * axisY;
        var tz = t * axisZ;
        var sx = s * axisX;
        var sy = s * axisY;
        var sz = s * axisZ;

        res[0] = tx * axisX + c;
        res[1] = tx * axisY - sz;
        res[2] = tx * axisZ + sy;
        res[3] = ty * axisX + sz;
        res[4] = ty * axisY + c;
        res[5] = ty * axisZ - sx;
        res[6] = tz * axisX - sy;
        res[7] = tz * axisY + sx;
        res[8] = tz * axisZ + c;

        return res;
    },
    m33FromQuat: function m33FromQuatFn(q, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isQuat(q)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx33(res)); */

        var qx = q[0];
        var qy = q[1];
        var qz = q[2];
        var qw = q[3];

        var xx = 2.0 * qx * qx;
        var yy = 2.0 * qy * qy;
        var zz = 2.0 * qz * qz;
        var xy = 2.0 * qx * qy;
        var zw = 2.0 * qz * qw;
        var xz = 2.0 * qx * qz;
        var yw = 2.0 * qy * qw;
        var yz = 2.0 * qy * qz;
        var xw = 2.0 * qx * qw;

        res[0] = 1.0 - yy - zz;
        res[1] = xy - zw;
        res[2] = xz + yw;
        res[3] = xy + zw;
        res[4] = 1.0 - xx - zz;
        res[5] = yz - xw;
        res[6] = xz - yw;
        res[7] = yz + xw;
        res[8] = 1.0 - xx - yy;

        return res;
    },
    m33Right: function m33RightFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[0];
        res[1] = m[1];
        res[2] = m[2];
        return res;
    },
    m33Up: function m33UpFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[3];
        res[1] = m[4];
        res[2] = m[5];
        return res;
    },
    m33At: function m33AtFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[6];
        res[1] = m[7];
        res[2] = m[8];
        return res;
    },
    m33SetRight: function m33SetRightFn(m, v) {
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */

        m[0] = v[0];
        m[1] = v[1];
        m[2] = v[2];
    },
    m33SetUp: function m33SetUpFn(m, v) {
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */

        m[3] = v[0];
        m[4] = v[1];
        m[5] = v[2];
    },
    m33SetAt: function m33SetAtFn(m, v) {
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */

        m[6] = v[0];
        m[7] = v[1];
        m[8] = v[2];
    },
    m33Transpose: function m33TransposeFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        dst[0] = m0;
        dst[1] = m3;
        dst[2] = m6;
        dst[3] = m1;
        dst[4] = m4;
        dst[5] = m7;
        dst[6] = m2;
        dst[7] = m5;
        dst[8] = m8;
        return dst;
    },
    m33Determinant: function m33DeterminantFn(m) {
        /* debug.assert(debug.isMtx33(m)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        return (m0 * (m4 * m8 - m5 * m7) + m1 * (m5 * m6 - m3 * m8) + m2 * (m3 * m7 - m4 * m6));
    },
    m33Inverse: function m33InverseFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        var d4857 = (m4 * m8 - m5 * m7);
        var d5638 = (m5 * m6 - m3 * m8);
        var d3746 = (m3 * m7 - m4 * m6);
        var det = (m0 * d4857 + m1 * d5638 + m2 * d3746);
        if (det === 0.0) {
            dst[0] = dst[1] = dst[2] = 0.0;
            dst[3] = dst[4] = dst[5] = 0.0;
            dst[6] = dst[7] = dst[8] = 0.0;
            return dst;
        } else {
            var m0 = m[0];
            var m1 = m[1];
            var m2 = m[2];
            var m3 = m[3];
            var m4 = m[4];
            var m5 = m[5];
            var m6 = m[6];
            var m7 = m[7];
            var m8 = m[8];

            var detrecp = 1.0 / det;
            dst[0] = (d4857 * detrecp);
            dst[1] = ((m7 * m2 - m8 * m1) * detrecp);
            dst[2] = ((m1 * m5 - m2 * m4) * detrecp);
            dst[3] = (d5638 * detrecp);
            dst[4] = ((m8 * m0 - m6 * m2) * detrecp);
            dst[5] = ((m3 * m2 - m0 * m5) * detrecp);
            dst[6] = (d3746 * detrecp);
            dst[7] = ((m6 * m1 - m7 * m0) * detrecp);
            dst[8] = ((m0 * m4 - m3 * m1) * detrecp);
            return dst;
        }
    },
    m33InverseTranspose: function m33InverseTransposeFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m) || debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && (debug.isMtx33(res) || debug.isMtx43(res))); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var d4857 = (m4 * m8 - m5 * m7);
        var d5638 = (m5 * m6 - m3 * m8);
        var d3746 = (m3 * m7 - m4 * m6);
        var det = (m0 * d4857 + m1 * d5638 + m2 * d3746);
        if (det === 0.0) {
            res[0] = res[1] = res[2] = 0.0;
            res[3] = res[4] = res[5] = 0.0;
            res[6] = res[7] = res[8] = 0.0;
        } else {
            var detrecp = 1.0 / det;
            res[0] = (d4857 * detrecp);
            res[3] = ((m7 * m2 - m8 * m1) * detrecp);
            res[6] = ((m1 * m5 - m2 * m4) * detrecp);
            res[1] = (d5638 * detrecp);
            res[4] = ((m8 * m0 - m6 * m2) * detrecp);
            res[7] = ((m3 * m2 - m0 * m5) * detrecp);
            res[2] = (d3746 * detrecp);
            res[5] = ((m6 * m1 - m7 * m0) * detrecp);
            res[8] = ((m0 * m4 - m3 * m1) * detrecp);
        }
        return res;
    },
    m33Mul: function m33MulFn(a, b, dst) {
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];

        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        dst[0] = (b0 * a0 + b3 * a1 + b6 * a2);
        dst[1] = (b1 * a0 + b4 * a1 + b7 * a2);
        dst[2] = (b2 * a0 + b5 * a1 + b8 * a2);

        dst[3] = (b0 * a3 + b3 * a4 + b6 * a5);
        dst[4] = (b1 * a3 + b4 * a4 + b7 * a5);
        dst[5] = (b2 * a3 + b5 * a4 + b8 * a5);

        dst[6] = (b0 * a6 + b3 * a7 + b6 * a8);
        dst[7] = (b1 * a6 + b4 * a7 + b7 * a8);
        dst[8] = (b2 * a6 + b5 * a7 + b8 * a8);

        return dst;
    },
    m33Transform: function m33TransformFn(m, v, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];
        dst[0] = (m[0] * v0 + m[3] * v1 + m[6] * v2);
        dst[1] = (m[1] * v0 + m[4] * v1 + m[7] * v2);
        dst[2] = (m[2] * v0 + m[5] * v1 + m[8] * v2);
        return dst;
    },
    m33Equal: function m33EqualFn(a, b, precision) {
        var abs = Math.abs;
        if (precision === undefined) {
            precision = this.precision;
        }
        /* debug.assert(debug.isMtx33(a)); */
        /* debug.assert(debug.isMtx33(b)); */
        /* debug.assert(debug.isNumber(precision)); */

        return (abs(a[0] - b[0]) <= precision && abs(a[1] - b[1]) <= precision && abs(a[2] - b[2]) <= precision && abs(a[3] - b[3]) <= precision && abs(a[4] - b[4]) <= precision && abs(a[5] - b[5]) <= precision && abs(a[6] - b[6]) <= precision && abs(a[7] - b[7]) <= precision && abs(a[8] - b[8]) <= precision);
    },
    m33MulM43: function m33MulM43Fn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx33(a)); */
        /* debug.assert(debug.isMtx43(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];

        dst[0] = b0 * a0 + b3 * a1 + b6 * a2;
        dst[1] = b1 * a0 + b4 * a1 + b7 * a2;
        dst[2] = b2 * a0 + b5 * a1 + b8 * a2;

        dst[3] = b0 * a3 + b3 * a4 + b6 * a5;
        dst[4] = b1 * a3 + b4 * a4 + b7 * a5;
        dst[5] = b2 * a3 + b5 * a4 + b8 * a5;

        dst[6] = b0 * a6 + b3 * a7 + b6 * a8;
        dst[7] = b1 * a6 + b4 * a7 + b7 * a8;
        dst[8] = b2 * a6 + b5 * a7 + b8 * a8;

        dst[9] = b[9];
        dst[10] = b[10];
        dst[11] = b[11];

        return dst;
    },
    m33MulM44: function m33MulM44Fn(a, b, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx33(a)); */
        /* debug.assert(debug.isMtx44(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];
        var b9 = b[9];
        var b10 = b[10];
        var b11 = b[11];

        dst[0] = b0 * a0 + b4 * a1 + b8 * a2;
        dst[1] = b1 * a0 + b5 * a1 + b9 * a2;
        dst[2] = b2 * a0 + b6 * a1 + b10 * a2;
        dst[3] = b3 * a0 + b7 * a1 + b11 * a2;

        dst[4] = b0 * a3 + b4 * a4 + b8 * a5;
        dst[5] = b1 * a3 + b5 * a4 + b9 * a5;
        dst[6] = b2 * a3 + b6 * a4 + b10 * a5;
        dst[7] = b3 * a3 + b7 * a4 + b11 * a5;

        dst[8] = b0 * a6 + b4 * a7 + b8 * a8;
        dst[9] = b1 * a6 + b5 * a7 + b9 * a8;
        dst[10] = b2 * a6 + b6 * a7 + b10 * a8;
        dst[11] = b3 * a6 + b7 * a7 + b11 * a8;

        dst[12] = b[12];
        dst[13] = b[13];
        dst[14] = b[14];
        dst[15] = b[15];

        return dst;
    },
    // Matrix3 operations with scalar
    m33ScalarAdd: function m33ScalarAddFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        for (var n = 0; n < 9; n += 1) {
            dst[n] = (m[n] + s);
        }
        return dst;
    },
    m33ScalarSub: function m33ScalarSubFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        for (var n = 0; n < 9; n += 1) {
            dst[n] = (m[n] - s);
        }
        return dst;
    },
    m33ScalarMul: function m33ScalarMulFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(9);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx33(dst)); */

        for (var n = 0; n < 9; n += 1) {
            dst[n] = (m[n] * s);
        }

        return dst;
    },
    // Matrix34
    m34BuildIdentity: function m34BuildIdentityFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx34(res)); */

        res[0] = 1.0;
        res[1] = 0.0;
        res[2] = 0.0;
        res[3] = 0.0;
        res[4] = 0.0;
        res[5] = 1.0;
        res[6] = 0.0;
        res[7] = 0.0;
        res[8] = 0.0;
        res[9] = 0.0;
        res[10] = 1.0;
        res[11] = 0.0;

        return res;
    },
    m34Pos: function m34PosFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx34(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec3(dst)); */

        dst[0] = m[3];
        dst[1] = m[7];
        dst[2] = m[11];
        return dst;
    },
    m34Scale: function m34ScaleFn(m, scale, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx34(m)); */
        /* debug.assert(debug.isVec3(scale)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx34(dst)); */

        var sx = scale[0];
        var sy = scale[1];
        var sz = scale[2];

        dst[0] = m[0] * sx;
        dst[1] = m[1] * sx;
        dst[2] = m[2] * sx;
        dst[3] = m[3];

        dst[4] = m[4] * sy;
        dst[5] = m[5] * sy;
        dst[6] = m[6] * sy;
        dst[7] = m[7];

        dst[8] = m[8] * sz;
        dst[9] = m[9] * sz;
        dst[10] = m[10] * sz;
        dst[11] = m[11];

        return dst;
    },
    // Matrix43
    m43BuildIdentity: function m43BuildIdentityFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = 1.0;
        res[1] = 0.0;
        res[2] = 0.0;
        res[3] = 0.0;
        res[4] = 1.0;
        res[5] = 0.0;
        res[6] = 0.0;
        res[7] = 0.0;
        res[8] = 1.0;
        res[9] = 0.0;
        res[10] = 0.0;
        res[11] = 0.0;

        return res;
    },
    m43Build: function m43BuildFn(r, u, a, p, dst, a21, a02, a12, a22, a03, a13, a23, _dst) {
        var res;
        var length = arguments.length;
        if (length >= 12) {
            if (length > 12) {
                res = arguments[12];
                if (res === undefined) {
                    res = new VMathArrayConstructor(12);
                }
            } else {
                res = new VMathArrayConstructor(12);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

            res[0] = arguments[0];
            res[1] = arguments[1];
            res[2] = arguments[2];
            res[3] = arguments[3];
            res[4] = arguments[4];
            res[5] = arguments[5];
            res[6] = arguments[6];
            res[7] = arguments[7];
            res[8] = arguments[8];
            res[9] = arguments[9];
            res[10] = arguments[10];
            res[11] = arguments[11];
        } else {
            res = dst;
            if (res === undefined) {
                res = new VMathArrayConstructor(12);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

            res[0] = r[0];
            res[1] = r[1];
            res[2] = r[2];
            res[3] = u[0];
            res[4] = u[1];
            res[5] = u[2];
            res[6] = a[0];
            res[7] = a[1];
            res[8] = a[2];
            res[9] = p[0];
            res[10] = p[1];
            res[11] = p[2];
        }

        return res;
    },
    m43BuildTranslation: function m43BuildTranslationFn(x, y, z, dst) {
        // Can NOT use p or dst because it will overwrite the input value...
        var res;
        if (3 <= arguments.length) {
            res = dst;
            if (res === undefined) {
                res = new VMathArrayConstructor(12);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

            res[9] = x;
            res[10] = y;
            res[11] = z;
        } else {
            res = y;
            if (res === undefined) {
                res = new VMathArrayConstructor(12);
            }
            /* debug.assert(debug.isVec3(x)); */
            /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

            res[9] = x[0];
            res[10] = x[1];
            res[11] = x[2];
        }

        res[0] = 1;
        res[1] = 0;
        res[2] = 0;
        res[3] = 0;
        res[4] = 1;
        res[5] = 0;
        res[6] = 0;
        res[7] = 0;
        res[8] = 1;

        return res;
    },
    m43Copy: function m43CopyFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = m[0];
        res[1] = m[1];
        res[2] = m[2];
        res[3] = m[3];
        res[4] = m[4];
        res[5] = m[5];
        res[6] = m[6];
        res[7] = m[7];
        res[8] = m[8];
        res[9] = m[9];
        res[10] = m[10];
        res[11] = m[11];

        return res;
    },
    m43FromM33V3: function m43FromM33V3Fn(m, v, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx33(m)); */
        /* debug.assert(debug.isVec3(v)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = m[0];
        res[1] = m[1];
        res[2] = m[2];
        res[3] = m[3];
        res[4] = m[4];
        res[5] = m[5];
        res[6] = m[6];
        res[7] = m[7];
        res[8] = m[8];
        res[9] = v[0];
        res[10] = v[1];
        res[11] = v[2];

        return res;
    },
    m43FromAxisRotation: function m43FromAxisRotationFn(axis, angle, dst) {
        var s = Math.sin(angle);
        var c = Math.cos(angle);
        var t = 1.0 - c;
        var axisX = axis[0];
        var axisY = axis[1];
        var axisZ = axis[2];
        var tx = t * axisX;
        var ty = t * axisY;
        var tz = t * axisZ;
        var sx = s * axisX;
        var sy = s * axisY;
        var sz = s * axisZ;

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isVec3(axis) || debug.isVec4(axis)); */
        /* debug.assert(debug.isNumber(angle)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = tx * axisX + c;
        res[1] = tx * axisY - sz;
        res[2] = tx * axisZ + sy;

        res[3] = ty * axisX + sz;
        res[4] = ty * axisY + c;
        res[5] = ty * axisZ - sx;

        res[6] = tz * axisX - sy;
        res[7] = tz * axisY + sx;
        res[8] = tz * axisZ + c;

        res[9] = 0.0;
        res[10] = 0.0;
        res[11] = 0.0;

        return res;
    },
    m43FromQuatPos: function m43FromQuatPosFn(qp, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isQuatPos(qp)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        var qx = qp[0];
        var qy = qp[1];
        var qz = qp[2];
        var qw = qp[3];
        var px = qp[4];
        var py = qp[5];
        var pz = qp[6];

        var xx = 2.0 * qx * qx;
        var yy = 2.0 * qy * qy;
        var zz = 2.0 * qz * qz;
        var xy = 2.0 * qx * qy;
        var zw = 2.0 * qz * qw;
        var xz = 2.0 * qx * qz;
        var yw = 2.0 * qy * qw;
        var yz = 2.0 * qy * qz;
        var xw = 2.0 * qx * qw;

        res[0] = 1.0 - yy - zz;
        res[1] = xy - zw;
        res[2] = xz + yw;

        res[3] = xy + zw;
        res[4] = 1.0 - xx - zz;
        res[5] = yz - xw;

        res[6] = xz - yw;
        res[7] = yz + xw;
        res[8] = 1.0 - xx - yy;

        res[9] = px;
        res[10] = py;
        res[11] = pz;

        return res;
    },
    m43FromRTS: function m43FromRTSFn(quat, pos, scale, dst) {
        var qx = quat[0];
        var qy = quat[1];
        var qz = quat[2];
        var qw = quat[3];

        var xx = (2.0 * qx * qx);
        var yy = (2.0 * qy * qy);
        var zz = (2.0 * qz * qz);
        var xy = (2.0 * qx * qy);
        var zw = (2.0 * qz * qw);
        var xz = (2.0 * qx * qz);
        var yw = (2.0 * qy * qw);
        var yz = (2.0 * qy * qz);
        var xw = (2.0 * qx * qw);

        var sx = scale[0];
        var sy = scale[1];
        var sz = scale[2];

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isQuat(quat)); */
        /* debug.assert(debug.isVec3(pos) || debug.isVec4(pos)); */
        /* debug.assert(debug.isVec3(scale)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = sx * (1.0 - yy - zz);
        res[1] = sx * (xy - zw);
        res[2] = sx * (xz + yw);
        res[3] = sy * (xy + zw);
        res[4] = sy * (1.0 - xx - zz);
        res[5] = sy * (yz - xw);
        res[6] = sz * (xz - yw);
        res[7] = sz * (yz + xw);
        res[8] = sz * (1.0 - xx - yy);
        res[9] = pos[0];
        res[10] = pos[1];
        res[11] = pos[2];

        return res;
    },
    m43FromRT: function m43FromRTFn(quat, pos, dst) {
        var qx = quat[0];
        var qy = quat[1];
        var qz = quat[2];
        var qw = quat[3];

        var xx = (2.0 * qx * qx);
        var yy = (2.0 * qy * qy);
        var zz = (2.0 * qz * qz);
        var xy = (2.0 * qx * qy);
        var zw = (2.0 * qz * qw);
        var xz = (2.0 * qx * qz);
        var yw = (2.0 * qy * qw);
        var yz = (2.0 * qy * qz);
        var xw = (2.0 * qx * qw);

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isQuat(quat)); */
        /* debug.assert(debug.isVec3(pos) || debug.isVec4(pos)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = 1.0 - yy - zz;
        res[1] = xy - zw;
        res[2] = xz + yw;
        res[3] = xy + zw;
        res[4] = 1.0 - xx - zz;
        res[5] = yz - xw;
        res[6] = xz - yw;
        res[7] = yz + xw;
        res[8] = 1.0 - xx - yy;
        res[9] = pos[0];
        res[10] = pos[1];
        res[11] = pos[2];

        return res;
    },
    m43Right: function m43RightFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[0];
        res[1] = m[1];
        res[2] = m[2];
        return res;
    },
    m43Up: function m43UpFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[3];
        res[1] = m[4];
        res[2] = m[5];
        return res;
    },
    m43At: function m43AtFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[6];
        res[1] = m[7];
        res[2] = m[8];
        return res;
    },
    m43Pos: function m43PosFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        res[0] = m[9];
        res[1] = m[10];
        res[2] = m[11];
        return res;
    },
    m43SetRight: function m43SetRightFn(m, v) {
        /* debug.assert(debug.isMtx43(m)); */

        m[0] = v[0];
        m[1] = v[1];
        m[2] = v[2];
    },
    m43SetUp: function m43SetUpFn(m, v) {
        /* debug.assert(debug.isMtx43(m)); */

        m[3] = v[0];
        m[4] = v[1];
        m[5] = v[2];
    },
    m43SetAt: function m43SetAtFn(m, v) {
        /* debug.assert(debug.isMtx43(m)); */

        m[6] = v[0];
        m[7] = v[1];
        m[8] = v[2];
    },
    m43SetPos: function m43SetPosFn(m, v) {
        /* debug.assert(debug.isMtx43(m)); */

        m[9] = v[0];
        m[10] = v[1];
        m[11] = v[2];
    },
    m43SetAxisRotation: function m43SetAxisRotationFn(m, axis, angle) {
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(axis) || debug.isVec4(axis)); */
        /* debug.assert(debug.isNumber(angle)); */

        var s = Math.sin(angle);
        var c = Math.cos(angle);
        var t = 1.0 - c;
        var axisX = axis[0];
        var axisY = axis[1];
        var axisZ = axis[2];
        var tx = t * axisX;
        var ty = t * axisY;
        var tz = t * axisZ;
        var sx = s * axisX;
        var sy = s * axisY;
        var sz = s * axisZ;
        m[0] = tx * axisX + c;
        m[1] = tx * axisY - sz;
        m[2] = tx * axisZ + sy;
        m[3] = ty * axisX + sz;
        m[4] = ty * axisY + c;
        m[5] = ty * axisZ - sx;
        m[6] = tz * axisX - sy;
        m[7] = tz * axisY + sx;
        m[8] = tz * axisZ + c;
    },
    m43InverseOrthonormal: function m43InverseOrthonormalFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var px = m[9];
        var py = m[10];
        var pz = m[11];
        dst[0] = m0;
        dst[1] = m3;
        dst[2] = m6;
        dst[3] = m1;
        dst[4] = m4;
        dst[5] = m7;
        dst[6] = m2;
        dst[7] = m5;
        dst[8] = m8;
        dst[9] = -((px * m0) + (py * m1) + (pz * m2));
        dst[10] = -((px * m3) + (py * m4) + (pz * m5));
        dst[11] = -((px * m6) + (py * m7) + (pz * m8));
        return dst;
    },
    m43Orthonormalize: function m43OrthonormalizeFn(m, dst) {
        /* debug.assert(debug.isMtx43(m)); */

        var normalize = VMath.v3Normalize;
        var length = VMath.v3Length;
        var dot = VMath.v3Dot;
        var cross = VMath.v3Cross;
        var abs = Math.abs;

        var right = VMath.m43Right(m);
        var up = VMath.m43Up(m);
        var at = VMath.m43At(m);
        var pos = VMath.m43Pos(m);

        var innerX = length(right);
        var innerY = length(up);
        var innerZ = length(at);

        normalize(right, right);
        normalize(up, up);
        normalize(at, at);

        var vpU, vpV, vpW;
        if (innerX > 0.0) {
            if (innerY > 0.0) {
                if (innerZ > 0.0) {
                    var outerX = abs(dot(up, at));
                    var outerY = abs(dot(at, right));
                    var outerZ = abs(dot(right, up));
                    if (outerX < outerY) {
                        if (outerX < outerZ) {
                            vpU = up;
                            vpV = at;
                            vpW = right;
                        } else {
                            vpU = right;
                            vpV = up;
                            vpW = at;
                        }
                    } else {
                        if (outerY < outerZ) {
                            vpU = at;
                            vpV = right;
                            vpW = up;
                        } else {
                            vpU = right;
                            vpV = up;
                            vpW = at;
                        }
                    }
                } else {
                    vpU = right;
                    vpV = up;
                    vpW = at;
                }
            } else {
                vpU = at;
                vpV = right;
                vpW = up;
            }
        } else {
            vpU = up;
            vpV = at;
            vpW = right;
        }

        cross(vpU, vpV, vpW);
        normalize(vpW, vpW);

        cross(vpW, vpU, vpV);
        normalize(vpV, vpV);

        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        dst[0] = right[0];
        dst[1] = right[1];
        dst[2] = right[2];
        dst[3] = up[0];
        dst[4] = up[1];
        dst[5] = up[2];
        dst[6] = at[0];
        dst[7] = at[1];
        dst[8] = at[2];
        dst[9] = pos[0];
        dst[10] = pos[1];
        dst[11] = pos[2];

        return dst;
    },
    m43Determinant: function m43DeterminantFn(m) {
        /* debug.assert(debug.isMtx43(m)); */
        return (m[0] * (m[4] * m[8] - m[5] * m[7]) + m[1] * (m[5] * m[6] - m[3] * m[8]) + m[2] * (m[3] * m[7] - m[4] * m[6]));
    },
    m43Inverse: function m43InverseFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];

        var d4857 = (m4 * m8 - m5 * m7);
        var d5638 = (m5 * m6 - m3 * m8);
        var d3746 = (m3 * m7 - m4 * m6);
        var det = (m0 * d4857 + m1 * d5638 + m2 * d3746);
        if (det === 0.0) {
            return dst;
        } else {
            if (dst === undefined) {
                dst = new VMathArrayConstructor(12);
            }
            var rdet = 1.0 / det;
            dst[0] = (d4857 * rdet);
            dst[1] = ((m7 * m2 - m8 * m1) * rdet);
            dst[2] = ((m1 * m5 - m2 * m4) * rdet);
            dst[3] = (d5638 * rdet);
            dst[4] = ((m8 * m0 - m6 * m2) * rdet);
            dst[5] = ((m3 * m2 - m0 * m5) * rdet);
            dst[6] = (d3746 * rdet);
            dst[7] = ((m6 * m1 - m7 * m0) * rdet);
            dst[8] = ((m0 * m4 - m3 * m1) * rdet);
            dst[9] = ((m3 * (m10 * m8 - m7 * m11) + m4 * (m6 * m11 - m9 * m8) + m5 * (m9 * m7 - m6 * m10)) * rdet);
            dst[10] = ((m6 * (m2 * m10 - m1 * m11) + m7 * (m0 * m11 - m9 * m2) + m8 * (m9 * m1 - m0 * m10)) * rdet);
            dst[11] = ((m9 * (m2 * m4 - m1 * m5) + m10 * (m0 * m5 - m3 * m2) + m11 * (m3 * m1 - m0 * m4)) * rdet);
            return dst;
        }
    },
    m43Translate: function m43TranslateFn(matrix, pos) {
        /* debug.assert(debug.isMtx43(matrix)); */
        /* debug.assert(debug.isVec3(pos) || debug.isVec4(pos)); */

        matrix[9] += pos[0];
        matrix[10] += pos[1];
        matrix[11] += pos[2];
    },
    m43Scale: function m43ScaleFn(m, scale, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(scale)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var sx = scale[0];
        var sy = scale[1];
        var sz = scale[2];

        dst[0] = m[0] * sx;
        dst[1] = m[1] * sx;
        dst[2] = m[2] * sx;
        dst[3] = m[3] * sy;
        dst[4] = m[4] * sy;
        dst[5] = m[5] * sy;
        dst[6] = m[6] * sz;
        dst[7] = m[7] * sz;
        dst[8] = m[8] * sz;
        dst[9] = m[9];
        dst[10] = m[10];
        dst[11] = m[11];

        return dst;
    },
    m43TransformVector: function m43TransformVectorFn(m, v, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];
        res[0] = (m[0] * v0 + m[3] * v1 + m[6] * v2);
        res[1] = (m[1] * v0 + m[4] * v1 + m[7] * v2);
        res[2] = (m[2] * v0 + m[5] * v1 + m[8] * v2);
        return res;
    },
    m43TransformPoint: function m43TransformPointFn(m, v, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */
        /* debug.assert(debug.isMathType(res) && debug.isVec3(res)); */

        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];
        res[0] = (m[0] * v0 + m[3] * v1 + m[6] * v2 + m[9]);
        res[1] = (m[1] * v0 + m[4] * v1 + m[7] * v2 + m[10]);
        res[2] = (m[2] * v0 + m[5] * v1 + m[8] * v2 + m[11]);
        return res;
    },
    m43Mul: function m43MulFn(a, b, dst) {
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];
        var a9 = a[9];
        var a10 = a[10];
        var a11 = a[11];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(a)); */
        /* debug.assert(debug.isMtx43(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = (b0 * a0 + b3 * a1 + b6 * a2);
        res[1] = (b1 * a0 + b4 * a1 + b7 * a2);
        res[2] = (b2 * a0 + b5 * a1 + b8 * a2);
        res[3] = (b0 * a3 + b3 * a4 + b6 * a5);
        res[4] = (b1 * a3 + b4 * a4 + b7 * a5);
        res[5] = (b2 * a3 + b5 * a4 + b8 * a5);
        res[6] = (b0 * a6 + b3 * a7 + b6 * a8);
        res[7] = (b1 * a6 + b4 * a7 + b7 * a8);
        res[8] = (b2 * a6 + b5 * a7 + b8 * a8);
        res[9] = (b0 * a9 + b3 * a10 + b6 * a11 + b[9]);
        res[10] = (b1 * a9 + b4 * a10 + b7 * a11 + b[10]);
        res[11] = (b2 * a9 + b5 * a10 + b8 * a11 + b[11]);

        return res;
    },
    m43MulM44: function m43MulM44Fn(a, b, dst) {
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];
        var a9 = a[9];
        var a10 = a[10];
        var a11 = a[11];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];
        var b9 = b[9];
        var b10 = b[10];
        var b11 = b[11];

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx43(a)); */
        /* debug.assert(debug.isMtx44(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx44(res)); */

        res[0] = (b0 * a0 + b4 * a1 + b8 * a2);
        res[1] = (b1 * a0 + b5 * a1 + b9 * a2);
        res[2] = (b2 * a0 + b6 * a1 + b10 * a2);
        res[3] = (b3 * a0 + b7 * a1 + b11 * a2);
        res[4] = (b0 * a3 + b4 * a4 + b8 * a5);
        res[5] = (b1 * a3 + b5 * a4 + b9 * a5);
        res[6] = (b2 * a3 + b6 * a4 + b10 * a5);
        res[7] = (b3 * a3 + b7 * a4 + b11 * a5);
        res[8] = (b0 * a6 + b4 * a7 + b8 * a8);
        res[9] = (b1 * a6 + b5 * a7 + b9 * a8);
        res[10] = (b2 * a6 + b6 * a7 + b10 * a8);
        res[11] = (b3 * a6 + b7 * a7 + b11 * a8);
        res[12] = (b0 * a9 + b4 * a10 + b8 * a11 + b[12]);
        res[13] = (b1 * a9 + b5 * a10 + b9 * a11 + b[13]);
        res[14] = (b2 * a9 + b6 * a10 + b10 * a11 + b[14]);
        res[15] = (b3 * a9 + b7 * a10 + b11 * a11 + b[15]);

        return res;
    },
    m43Transpose: function m43TransposeFn(m, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];

        res[0] = m0;
        res[1] = m3;
        res[2] = m6;
        res[3] = m9;
        res[4] = m1;
        res[5] = m4;
        res[6] = m7;
        res[7] = m10;
        res[8] = m2;
        res[9] = m5;
        res[10] = m8;
        res[11] = m11;

        return res;
    },
    m43MulTranspose: function m43MulTransposeFn(a, b, dst) {
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];
        var a9 = a[9];
        var a10 = a[10];
        var a11 = a[11];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];
        var b9 = b[9];
        var b10 = b[10];
        var b11 = b[11];

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(a)); */
        /* debug.assert(debug.isMtx43(b)); */
        /* debug.assert(debug.isMathType(res) && debug.isMtx43(res)); */

        res[0] = (b0 * a0 + b3 * a1 + b6 * a2);
        res[1] = (b0 * a3 + b3 * a4 + b6 * a5);
        res[2] = (b0 * a6 + b3 * a7 + b6 * a8);
        res[3] = (b0 * a9 + b3 * a10 + b6 * a11 + b9);
        res[4] = (b1 * a0 + b4 * a1 + b7 * a2);
        res[5] = (b1 * a3 + b4 * a4 + b7 * a5);
        res[6] = (b1 * a6 + b4 * a7 + b7 * a8);
        res[7] = (b1 * a9 + b4 * a10 + b7 * a11 + b10);
        res[8] = (b2 * a0 + b5 * a1 + b8 * a2);
        res[9] = (b2 * a3 + b5 * a4 + b8 * a5);
        res[10] = (b2 * a6 + b5 * a7 + b8 * a8);
        res[11] = (b2 * a9 + b5 * a10 + b8 * a11 + b11);

        return res;
    },
    m43Offset: function m43OffsetFn(m, o, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(o) || debug.isVec4(o)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];

        var o0 = o[0];
        var o1 = o[1];
        var o2 = o[2];

        dst[0] = m0;
        dst[1] = m1;
        dst[2] = m2;
        dst[3] = m3;
        dst[4] = m4;
        dst[5] = m5;
        dst[6] = m6;
        dst[7] = m7;
        dst[8] = m8;
        dst[9] = (m0 * o0 + m3 * o1 + m6 * o2 + m9);
        dst[10] = (m1 * o0 + m4 * o1 + m7 * o2 + m10);
        dst[11] = (m2 * o0 + m5 * o1 + m8 * o2 + m11);

        return dst;
    },
    m43NegOffset: function m43NegOffsetFn(m, o, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(o) || debug.isVec4(o)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];

        var o0 = -o[0];
        var o1 = -o[1];
        var o2 = -o[2];

        dst[0] = m0;
        dst[1] = m1;
        dst[2] = m2;
        dst[3] = m3;
        dst[4] = m4;
        dst[5] = m5;
        dst[6] = m6;
        dst[7] = m7;
        dst[8] = m8;
        dst[9] = (m0 * o0 + m3 * o1 + m6 * o2 + m9);
        dst[10] = (m1 * o0 + m4 * o1 + m7 * o2 + m10);
        dst[11] = (m2 * o0 + m5 * o1 + m8 * o2 + m11);

        return dst;
    },
    m43InverseTransposeProjection: function m43InverseTransposeProjectionFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isVec3(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        var xf = (0.5 / s[0]);
        var yf = (0.5 / s[1]);
        var zf = (0.5 / s[2]);
        var m0 = (m[0] * xf);
        var m1 = (m[1] * xf);
        var m2 = (m[2] * xf);
        var m3 = (m[3] * yf);
        var m4 = (m[4] * yf);
        var m5 = (m[5] * yf);
        var m6 = (m[6] * zf);
        var m7 = (m[7] * zf);
        var m8 = (m[8] * zf);
        var px = m[9];
        var py = m[10];
        var pz = m[11];

        dst[0] = m0;
        dst[1] = m1;
        dst[2] = m2;
        dst[3] = (0.5 - ((px * m0) + (py * m1) + (pz * m2)));
        dst[4] = m3;
        dst[5] = m4;
        dst[6] = m5;
        dst[7] = (0.5 - ((px * m3) + (py * m4) + (pz * m5)));
        dst[8] = m6;
        dst[9] = m7;
        dst[10] = m8;
        dst[11] = (0.5 - ((px * m6) + (py * m7) + (pz * m8)));

        return dst;
    },
    // Matrix 43 opeations with scalar
    m43ScalarAdd: function m43ScalarAddFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        for (var n = 0; n < 12; n += 1) {
            dst[n] = (m[n] + s);
        }
        return dst;
    },
    m43ScalarSub: function m43ScalarSubFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        for (var n = 0; n < 12; n += 1) {
            dst[n] = (m[n] - s);
        }
        return dst;
    },
    m43ScalarMul: function m43ScalarMulFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(12);
        }
        /* debug.assert(debug.isMtx43(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx43(dst)); */

        for (var n = 0; n < 12; n += 1) {
            dst[n] = (m[n] * s);
        }
        return dst;
    },
    // Matrix44
    m44BuildIdentity: function m44BuildIdentityFn(dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMathType(res) && debug.isMtx44(res)); */

        res[0] = 1.0;
        res[1] = 0.0;
        res[2] = 0.0;
        res[3] = 0.0;
        res[4] = 0.0;
        res[5] = 1.0;
        res[6] = 0.0;
        res[7] = 0.0;
        res[8] = 0.0;
        res[9] = 0.0;
        res[10] = 1.0;
        res[11] = 0.0;
        res[12] = 0.0;
        res[13] = 0.0;
        res[14] = 0.0;
        res[15] = 1.0;

        return res;
    },
    m44Build: function m44BuildFn(r, u, a, p, dst) {
        var res;
        var length = arguments.length;
        if (length >= 16) {
            if (length > 16) {
                res = arguments[16];
                if (res === undefined) {
                    res = new VMathArrayConstructor(16);
                }
            } else {
                res = new VMathArrayConstructor(16);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx44(res)); */

            res[0] = arguments[0];
            res[1] = arguments[1];
            res[2] = arguments[2];
            res[3] = arguments[3];
            res[4] = arguments[4];
            res[5] = arguments[5];
            res[6] = arguments[6];
            res[7] = arguments[7];
            res[8] = arguments[8];
            res[9] = arguments[9];
            res[10] = arguments[10];
            res[11] = arguments[11];
            res[12] = arguments[12];
            res[13] = arguments[13];
            res[14] = arguments[14];
            res[15] = arguments[15];
        } else {
            res = dst;
            if (res === undefined) {
                res = new VMathArrayConstructor(16);
            }
            /* debug.assert(debug.isMathType(res) && debug.isMtx44(res)); */

            res[0] = r[0];
            res[1] = r[1];
            res[2] = r[2];
            res[3] = r[3];
            res[4] = u[0];
            res[5] = u[1];
            res[6] = u[2];
            res[7] = u[3];
            res[8] = a[0];
            res[9] = a[1];
            res[10] = a[2];
            res[11] = a[3];
            res[12] = p[0];
            res[13] = p[1];
            res[14] = p[2];
            res[15] = p[3];
        }

        return res;
    },
    m44Copy: function m44CopyFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        dst[0] = m[0];
        dst[1] = m[1];
        dst[2] = m[2];
        dst[3] = m[3];
        dst[4] = m[4];
        dst[5] = m[5];
        dst[6] = m[6];
        dst[7] = m[7];
        dst[8] = m[8];
        dst[9] = m[9];
        dst[10] = m[10];
        dst[11] = m[11];
        dst[12] = m[12];
        dst[13] = m[13];
        dst[14] = m[14];
        dst[15] = m[15];

        return dst;
    },
    m44Right: function m44RightFn(m, dst) {
        if (dst === undefined) {
            return m.slice(0, 4);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = m[0];
        dst[1] = m[1];
        dst[2] = m[2];
        dst[3] = m[3];
        return dst;
    },
    m44Up: function m44UpFn(m, dst) {
        if (dst === undefined) {
            return m.slice(4, 8);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = m[4];
        dst[1] = m[5];
        dst[2] = m[6];
        dst[3] = m[7];
        return dst;
    },
    m44At: function m44AtFn(m, dst) {
        if (dst === undefined) {
            return m.slice(8, 12);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = m[8];
        dst[1] = m[9];
        dst[2] = m[10];
        dst[3] = m[11];
        return dst;
    },
    m44Pos: function m44PosFn(m, dst) {
        if (dst === undefined) {
            return m.slice(12);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        dst[0] = m[12];
        dst[1] = m[13];
        dst[2] = m[14];
        dst[3] = m[15];
        return dst;
    },
    m44SetRight: function m44SetRightFn(m, v) {
        /* debug.assert(debug.isMtx44(m)); */

        m[0] = v[0];
        m[1] = v[1];
        m[2] = v[2];
        m[3] = v[3];
    },
    m44SetUp: function m44SetUpFn(m, v) {
        /* debug.assert(debug.isMtx44(m)); */

        m[4] = v[0];
        m[5] = v[1];
        m[6] = v[2];
        m[7] = v[3];
    },
    m44SetAt: function m44SetAtFn(m, v) {
        /* debug.assert(debug.isMtx44(m)); */

        m[8] = v[0];
        m[9] = v[1];
        m[10] = v[2];
        m[11] = v[3];
    },
    m44SetPos: function m44SetPosFn(m, v) {
        /* debug.assert(debug.isMtx44(m)); */

        m[12] = v[0];
        m[13] = v[1];
        m[14] = v[2];
        m[15] = v[3];
    },
    m44Translate: function m44TranslateFn(m, v) {
        /* debug.assert(debug.isMtx44(m)); */

        m[12] += v[0];
        m[13] += v[1];
        m[14] += v[2];
        m[15] += v[3];
    },
    m44Scale: function m44ScaleFn(m, scale, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isVec3(scale)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        dst[0] = m[0] * scale[0];
        dst[1] = m[1] * scale[0];
        dst[2] = m[2] * scale[0];
        dst[3] = m[3];
        dst[4] = m[4] * scale[1];
        dst[5] = m[5] * scale[1];
        dst[6] = m[6] * scale[1];
        dst[7] = m[7];
        dst[8] = m[8] * scale[2];
        dst[9] = m[9] * scale[2];
        dst[10] = m[10] * scale[2];
        dst[11] = m[11];
        dst[12] = m[12];
        dst[13] = m[13];
        dst[14] = m[14];
        dst[15] = m[15];

        return dst;
    },
    m44Transform: function m44TransformFn(m, v, dst) {
        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];
        var v3 = v[3];
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isVec4(v)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        if (v3 !== 1.0) {
            dst[0] = ((m[0] * v0) + (m[4] * v1) + (m[8] * v2) + (m[12] * v3));
            dst[1] = ((m[1] * v0) + (m[5] * v1) + (m[9] * v2) + (m[13] * v3));
            dst[2] = ((m[2] * v0) + (m[6] * v1) + (m[10] * v2) + (m[14] * v3));
            dst[3] = ((m[3] * v0) + (m[7] * v1) + (m[11] * v2) + (m[15] * v3));
        } else {
            dst[0] = ((m[0] * v0) + (m[4] * v1) + (m[8] * v2) + m[12]);
            dst[1] = ((m[1] * v0) + (m[5] * v1) + (m[9] * v2) + m[13]);
            dst[2] = ((m[2] * v0) + (m[6] * v1) + (m[10] * v2) + m[14]);
            dst[3] = ((m[3] * v0) + (m[7] * v1) + (m[11] * v2) + m[15]);
        }
        return dst;
    },
    m44Mul: function m44MulFn(a, b, dst) {
        var a0 = a[0];
        var a1 = a[1];
        var a2 = a[2];
        var a3 = a[3];
        var a4 = a[4];
        var a5 = a[5];
        var a6 = a[6];
        var a7 = a[7];
        var a8 = a[8];
        var a9 = a[9];
        var a10 = a[10];
        var a11 = a[11];
        var a12 = a[12];
        var a13 = a[13];
        var a14 = a[14];
        var a15 = a[15];

        var b0 = b[0];
        var b1 = b[1];
        var b2 = b[2];
        var b3 = b[3];
        var b4 = b[4];
        var b5 = b[5];
        var b6 = b[6];
        var b7 = b[7];
        var b8 = b[8];
        var b9 = b[9];
        var b10 = b[10];
        var b11 = b[11];
        var b12 = b[12];
        var b13 = b[13];
        var b14 = b[14];
        var b15 = b[15];

        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(a)); */
        /* debug.assert(debug.isMtx44(b)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        dst[0] = (b0 * a0 + b4 * a1 + b8 * a2 + b12 * a3);
        dst[1] = (b1 * a0 + b5 * a1 + b9 * a2 + b13 * a3);
        dst[2] = (b2 * a0 + b6 * a1 + b10 * a2 + b14 * a3);
        dst[3] = (b3 * a0 + b7 * a1 + b11 * a2 + b15 * a3);
        dst[4] = (b0 * a4 + b4 * a5 + b8 * a6 + b12 * a7);
        dst[5] = (b1 * a4 + b5 * a5 + b9 * a6 + b13 * a7);
        dst[6] = (b2 * a4 + b6 * a5 + b10 * a6 + b14 * a7);
        dst[7] = (b3 * a4 + b7 * a5 + b11 * a6 + b15 * a7);
        dst[8] = (b0 * a8 + b4 * a9 + b8 * a10 + b12 * a11);
        dst[9] = (b1 * a8 + b5 * a9 + b9 * a10 + b13 * a11);
        dst[10] = (b2 * a8 + b6 * a9 + b10 * a10 + b14 * a11);
        dst[11] = (b3 * a8 + b7 * a9 + b11 * a10 + b15 * a11);
        dst[12] = (b0 * a12 + b4 * a13 + b8 * a14 + b12 * a15);
        dst[13] = (b1 * a12 + b5 * a13 + b9 * a14 + b13 * a15);
        dst[14] = (b2 * a12 + b6 * a13 + b10 * a14 + b14 * a15);
        dst[15] = (b3 * a12 + b7 * a13 + b11 * a14 + b15 * a15);

        return dst;
    },
    m44Inverse: function m44InverseFn(m, dst) {
        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];
        var m12 = m[12];
        var m13 = m[13];
        var m14 = m[14];
        var m15 = m[15];

        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        var A0 = ((m0 * m5) - (m1 * m4));
        var A1 = ((m0 * m6) - (m2 * m4));
        var A2 = ((m0 * m7) - (m3 * m4));
        var A3 = ((m1 * m6) - (m2 * m5));
        var A4 = ((m1 * m7) - (m3 * m5));
        var A5 = ((m2 * m7) - (m3 * m6));
        var B0 = ((m8 * m13) - (m9 * m12));
        var B1 = ((m8 * m14) - (m10 * m12));
        var B2 = ((m8 * m15) - (m11 * m12));
        var B3 = ((m9 * m14) - (m10 * m13));
        var B4 = ((m9 * m15) - (m11 * m13));
        var B5 = ((m10 * m15) - (m11 * m14));

        var det = ((A0 * B5) - (A1 * B4) + (A2 * B3) + (A3 * B2) - (A4 * B1) + (A5 * B0));
        if (det === 0.0) {
            dst[0] = 0.0;
            dst[1] = 0.0;
            dst[2] = 0.0;
            dst[3] = 0.0;
            dst[4] = 0.0;
            dst[5] = 0.0;
            dst[6] = 0.0;
            dst[7] = 0.0;
            dst[8] = 0.0;
            dst[9] = 0.0;
            dst[10] = 0.0;
            dst[11] = 0.0;
            dst[12] = 0.0;
            dst[13] = 0.0;
            dst[14] = 0.0;
            dst[15] = 0.0;
        } else {
            var detrecp = 1.0 / det;
            dst[0] = (+(m5 * B5) - (m6 * B4) + (m7 * B3)) * detrecp;
            dst[4] = (-(m4 * B5) + (m6 * B2) - (m7 * B1)) * detrecp;
            dst[8] = (+(m4 * B4) - (m5 * B2) + (m7 * B0)) * detrecp;
            dst[12] = (-(m4 * B3) + (m5 * B1) - (m6 * B0)) * detrecp;
            dst[1] = (-(m1 * B5) + (m2 * B4) - (m3 * B3)) * detrecp;
            dst[5] = (+(m0 * B5) - (m2 * B2) + (m3 * B1)) * detrecp;
            dst[9] = (-(m0 * B4) + (m1 * B2) - (m3 * B0)) * detrecp;
            dst[13] = (+(m0 * B3) - (m1 * B1) + (m2 * B0)) * detrecp;
            dst[2] = (+(m13 * A5) - (m14 * A4) + (m15 * A3)) * detrecp;
            dst[6] = (-(m12 * A5) + (m14 * A2) - (m15 * A1)) * detrecp;
            dst[10] = (+(m12 * A4) - (m13 * A2) + (m15 * A0)) * detrecp;
            dst[14] = (-(m12 * A3) + (m13 * A1) - (m14 * A0)) * detrecp;
            dst[3] = (-(m9 * A5) + (m10 * A4) - (m11 * A3)) * detrecp;
            dst[7] = (+(m8 * A5) - (m10 * A2) + (m11 * A1)) * detrecp;
            dst[11] = (-(m8 * A4) + (m9 * A2) - (m11 * A0)) * detrecp;
            dst[15] = (+(m8 * A3) - (m9 * A1) + (m10 * A0)) * detrecp;
            /*jsline white: true */
        }

        return dst;
    },
    m44Transpose: function m44TransposeFn(m, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        dst[0] = m[0];
        dst[1] = m[4];
        dst[2] = m[8];
        dst[3] = m[12];
        dst[4] = m[1];
        dst[5] = m[5];
        dst[6] = m[9];
        dst[7] = m[13];
        dst[8] = m[2];
        dst[9] = m[6];
        dst[10] = m[10];
        dst[11] = m[14];
        dst[12] = m[3];
        dst[13] = m[7];
        dst[14] = m[11];
        dst[15] = m[15];

        return dst;
    },
    // Matrix44 operations with scalars
    m44ScalarAdd: function m44ScalarAddFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        for (var n = 0; n < 16; n += 1) {
            dst[n] = (m[n] + s);
        }
        return dst;
    },
    m44ScalarSub: function m44ScalarSubFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        for (var n = 0; n < 16; n += 1) {
            dst[n] = (m[n] - s);
        }
        return dst;
    },
    m44ScalarMul: function m44ScalarMulFn(m, s, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(16);
        }
        /* debug.assert(debug.isMtx44(m)); */
        /* debug.assert(debug.isNumber(s)); */
        /* debug.assert(debug.isMathType(dst) && debug.isMtx44(dst)); */

        for (var n = 0; n < 16; n += 1) {
            dst[n] = (m[n] * s);
        }
        return dst;
    },
    // Quaternion
    quatBuild: function quatBuildFn(x, y, z, w, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isNumber(x)); */
        /* debug.assert(debug.isNumber(y)); */
        /* debug.assert(debug.isNumber(z)); */
        /* debug.assert(debug.isNumber(w)); */
        /* debug.assert(debug.isMathType(res) && debug.isQuat(res)); */

        res[0] = x;
        res[1] = y;
        res[2] = z;
        res[3] = w;
        return res;
    },
    quatCopy: function quatCopyFn(src, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isMathType(res) && debug.isQuat(res)); */

        res[0] = src[0];
        res[1] = src[1];
        res[2] = src[2];
        res[3] = src[3];
        return res;
    },
    quatIsSimilar: function quatIsSimilarFn(q1, q2, precision) {
        if (precision === undefined) {
            precision = this.precision;
        }
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        /* debug.assert(debug.isNumber(precision)); */

        // this compares for similar rotations not raw data
        var q1temp = q1;

        if (q1[3] * q2[3] < 0.0) {
            // quaternions in opposing hemispheres, negate one
            q1temp = VMath.v4Neg(q1);
        }

        var mag_sqrd = VMath.v4LengthSq(VMath.v4Sub(q1temp, q2));
        var epsilon_sqrd = (precision * precision);
        return mag_sqrd < epsilon_sqrd;
    },
    quatLength: function quatLengthFn(q) {
        /* debug.assert(debug.isQuat(q)); */
        return VMath.v4Length(q);
    },
    quatDot: function quatDotFn(q1, q2) {
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        return VMath.v4Dot(q1, q2);
    },
    quatMul: function quatMulFn(q1, q2, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        /* debug.assert(debug.isMathType(dst) && debug.isQuat(dst)); */

        // Note quaternion multiplication is the opposite way around from our matrix multiplication
        //var v1 = q1; // use full quats to avoid copy
        //var v2 = q2;
        /*
        // Calculate the imaginary part
        var quat = VMath.v3Add3(VMath.v3ScalarMul(v2, q1[3]), VMath.v3ScalarMul(v1, q2[3]), VMath.v3Cross(v1, v2));
        // And extend with the real part
        quat[3] = (q1[3] * q2[3]) - VMath.v3Dot(v1, v2);
        */
        // Inlined from above
        var q2x = q1[0];
        var q2y = q1[1];
        var q2z = q1[2];
        var q2w = q1[3];
        var q1x = q2[0];
        var q1y = q2[1];
        var q1z = q2[2];
        var q1w = q2[3];

        var cx = (q1z * q2y) - (q1y * q2z);
        var cy = (q1x * q2z) - (q1z * q2x);
        var cz = (q1y * q2x) - (q1x * q2y);

        dst[0] = (q2x * q1w) + (q1x * q2w) + cx;
        dst[1] = (q2y * q1w) + (q1y * q2w) + cy;
        dst[2] = (q2z * q1w) + (q1z * q2w) + cz;
        dst[3] = (q1w * q2w) - (q1x * q2x + q1y * q2y + q1z * q2z);

        return dst;
    },
    quatMulTranslate: function quatMulTranslateFn(qa, va, qb, vb, qr, vr) {
        /* debug.assert(debug.isQuat(qa)); */
        /* debug.assert(debug.isVec3(va) || debug.isVec4(va)); */
        /* debug.assert(debug.isQuat(qb)); */
        /* debug.assert(debug.isVec3(vb) || debug.isVec4(vb)); */
        /* debug.assert(debug.isQuat(qr)); */
        /* debug.assert(debug.isVec3(vr) || debug.isVec4(vr)); */

        var qax = qa[0];
        var qay = qa[1];
        var qaz = qa[2];
        var qaw = qa[3];
        var qbx = qb[0];
        var qby = qb[1];
        var qbz = qb[2];
        var qbw = qb[3];

        // Multiply together the two quaternions
        var cx = (qaz * qby) - (qay * qbz);
        var cy = (qax * qbz) - (qaz * qbx);
        var cz = (qay * qbx) - (qax * qby);

        qr[0] = (qbx * qaw) + (qax * qbw) + cx;
        qr[1] = (qby * qaw) + (qay * qbw) + cy;
        qr[2] = (qbz * qaw) + (qaz * qbw) + cz;
        qr[3] = (qaw * qbw) - (qax * qbx + qay * qby + qaz * qbz);

        // Transform the 2nd vector by the first quaternion and add in the first position
        var vax = va[0];
        var vay = va[1];
        var vaz = va[2];
        var vbx = vb[0];
        var vby = vb[1];
        var vbz = vb[2];

        var s = (qaw * qaw) - (qax * qax + qay * qay + qaz * qaz);
        var rx = vbx * s;
        var ry = vby * s;
        var rz = vbz * s;

        s = qax * vbx + qay * vby + qaz * vbz;

        var twoS = s + s;
        rx += qax * twoS;
        ry += qay * twoS;
        rz += qaz * twoS;

        cx = (qaz * vby) - (qay * vbz);
        cy = (qax * vbz) - (qaz * vbx);
        cz = (qay * vbx) - (qax * vby);
        var twoQw = qaw + qaw;
        rx += cx * twoQw;
        ry += cy * twoQw;
        rz += cz * twoQw;

        vr[0] = rx + vax;
        vr[1] = ry + vay;
        vr[2] = rz + vaz;
    },
    quatNormalize: function quatNormalizeFn(q, dst) {
        /* debug.assert(debug.isQuat(q)); */

        var norme = VMath.quatDot(q, q);
        if (norme === 0.0) {
            return VMath.v4BuildZero(dst);
        } else {
            var recip = 1.0 / Math.sqrt(norme);
            return VMath.v4ScalarMul(q, recip, dst);
        }
    },
    quatConjugate: function quatConjugateFn(q, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isQuat(q)); */
        /* debug.assert(debug.isMathType(dst) && debug.isQuat(dst)); */

        dst[0] = -q[0];
        dst[1] = -q[1];
        dst[2] = -q[2];
        dst[3] = q[3];

        return dst;
    },
    quatLerp: function quatLerpFn(q1, q2, t, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        /* debug.assert(debug.isNumber(t)); */
        /* debug.assert(debug.isMathType(dst) && debug.isQuat(dst)); */

        var q1x = q1[0];
        var q1y = q1[1];
        var q1z = q1[2];
        var q1w = q1[3];
        var q2x = q2[0];
        var q2y = q2[1];
        var q2z = q2[2];
        var q2w = q2[3];

        dst[0] = ((q2x - q1x) * t) + q1x;
        dst[1] = ((q2y - q1y) * t) + q1y;
        dst[2] = ((q2z - q1z) * t) + q1z;
        dst[3] = ((q2w - q1w) * t) + q1w;

        return dst;
    },
    cosMinSlerpAngle: Math.cos(Math.PI / 40.0),
    quatSlerp: function quatSlerpFn(q1, q2, t, dst) {
        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        /* debug.assert(debug.isNumber(t)); */
        /* debug.assert(debug.isMathType(res) && debug.isQuat(res)); */

        var q1x = q1[0];
        var q1y = q1[1];
        var q1z = q1[2];
        var q1w = q1[3];
        var q2x = q2[0];
        var q2y = q2[1];
        var q2z = q2[2];
        var q2w = q2[3];
        var dotq1q2 = (q1x * q2x) + (q1y * q2y) + (q1z * q2z) + (q1w * q2w);

        var cosom = dotq1q2;
        if (cosom < 0.0) {
            q1x = -q1x;
            q1y = -q1y;
            q1z = -q1z;
            q1w = -q1w;
            cosom = -cosom;
        }

        if (cosom > VMath.cosMinSlerpAngle) {
            if (cosom > (1.0 - 1e-6)) {
                res[0] = q1x;
                res[1] = q1y;
                res[2] = q1z;
                res[3] = q1w;

                return res;
            }

            var delta = t;
            if (dotq1q2 <= 0.0) {
                delta = -t;
            }

            var qrx = ((q2x - q1x) * delta) + q1x;
            var qry = ((q2y - q1y) * delta) + q1y;
            var qrz = ((q2z - q1z) * delta) + q1z;
            var qrw = ((q2w - q1w) * delta) + q1w;

            var mag = Math.sqrt((qrx * qrx) + (qry * qry) + (qrz * qrz) + (qrw * qrw));
            var recip = 1.0 / mag;

            res[0] = qrx * recip;
            res[1] = qry * recip;
            res[2] = qrz * recip;
            res[3] = qrw * recip;

            return res;
        }

        var sinFn = Math.sin;
        var omega = Math.acos(cosom);
        var inv_sin_omega = 1.0 / sinFn(omega);

        var scalar = sinFn((1.0 - t) * omega) * inv_sin_omega;
        q1x = q1x * scalar;
        q1y = q1y * scalar;
        q1z = q1z * scalar;
        q1w = q1w * scalar;

        scalar = sinFn(t * omega) * inv_sin_omega;
        q2x = q2x * scalar;
        q2y = q2y * scalar;
        q2z = q2z * scalar;
        q2w = q2w * scalar;

        res[0] = q1x + q2x;
        res[1] = q1y + q2y;
        res[2] = q1z + q2z;
        res[3] = q1w + q2w;

        return res;
    },
    quatFromM43: function quatFromM43Fn(m, dst) {
        /* debug.assert(debug.isMtx43(m)); */

        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];

        var x, y, z, w, s;
        var trace = m0 + m4 + m8 + 1;
        if (trace > VMath.precision) {
            w = Math.sqrt(trace) / 2;
            x = (m5 - m7) / (4 * w);
            y = (m6 - m2) / (4 * w);
            z = (m1 - m3) / (4 * w);
        } else {
            if ((m0 > m4) && (m0 > m8)) {
                s = Math.sqrt(1.0 + m0 - m4 - m8) * 2;
                w = (m5 - m7) / s;
                x = 0.25 * s;
                y = (m3 + m1) / s;
                z = (m6 + m2) / s;
            } else if (m4 > m8) {
                s = Math.sqrt(1.0 + m4 - m0 - m8) * 2;
                w = (m6 - m2) / s;
                x = (m3 + m1) / s;
                y = 0.25 * s;
                z = (m7 + m5) / s;
            } else {
                s = Math.sqrt(1.0 + m8 - m0 - m4) * 2;
                w = (m1 - m3) / s;
                x = (m6 + m2) / s;
                y = (m7 + m5) / s;
                z = 0.25 * s;
            }
        }

        var q = VMath.quatNormalize([x, y, z, w], dst);

        return VMath.quatConjugate(q, dst);
    },
    quatFromAxisRotation: function quatFromAxisRotationFn(axis, angle, dst) {
        var omega = 0.5 * angle;
        var s = Math.sin(omega);
        var c = Math.cos(omega);

        var res = dst;
        if (res === undefined) {
            res = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isVec3(axis) || debug.isVec4(axis)); */
        /* debug.assert(debug.isNumber(angle)); */
        /* debug.assert(debug.isMathType(res) && debug.isQuat(res)); */

        res[0] = axis[0] * s;
        res[1] = axis[1] * s;
        res[2] = axis[2] * s;
        res[3] = c;

        return VMath.quatNormalize(res, res);
    },
    quatToAxisRotation: function quatToAxisRotation(q, dst) {
        if (dst === undefined) {
            dst = new VMathArrayConstructor(4);
        }
        /* debug.assert(debug.isQuat(q)); */
        /* debug.assert(debug.isMathType(dst) && debug.isVec4(dst)); */

        var q3 = q[3];
        var angle = Math.acos(q3) * 2.0;
        var sin_sqrd = 1.0 - q3 * q3;

        if (sin_sqrd < VMath.precision) {
            // we can return any axis
            dst[0] = 1.0;
            dst[1] = 0.0;
            dst[2] = 0.0;
            dst[3] = angle;
        } else {
            var scale = 1.0 / Math.sqrt(sin_sqrd);
            dst[0] = q[0] * scale;
            dst[1] = q[1] * scale;
            dst[2] = q[2] * scale;
            dst[3] = angle;
        }
        return dst;
    },
    quatTransformVector: function quatTransformVectorFn(q, v, dst) {
        /*
        var qimaginary = q; // Use full quat directly to avoid copy
        var qw = q[3];
        
        var s = (qw * qw) - VMath.v3Dot(qimaginary, qimaginary);
        
        var r = VMath.v3ScalarMul(v, s);
        
        s = VMath.v3Dot(qimaginary, v);
        r = VMath.v3Add(r, VMath.v3ScalarMul(qimaginary, s + s));
        r = VMath.v3Add(r, VMath.v3ScalarMul(VMath.v3Cross(qimaginary, v), qw + qw));
        */
        /* debug.assert(debug.isQuat(q)); */
        /* debug.assert(debug.isVec3(v) || debug.isVec4(v)); */

        // Inlined from above
        var qx = q[0];
        var qy = q[1];
        var qz = q[2];
        var qw = q[3];

        var vx = v[0];
        var vy = v[1];
        var vz = v[2];

        //var s = (qw * qw) - VMath.v3Dot(qimaginary, qimaginary);
        var s = (qw * qw) - (qx * qx + qy * qy + qz * qz);

        //var r = VMath.v3ScalarMul(v, s);
        var rx = vx * s;
        var ry = vy * s;
        var rz = vz * s;

        //s = VMath.v3Dot(qimaginary, v);
        s = qx * vx + qy * vy + qz * vz;

        //r = VMath.v3Add(r, VMath.v3ScalarMul(qimaginary, s + s));
        var twoS = s + s;
        rx += qx * twoS;
        ry += qy * twoS;
        rz += qz * twoS;

        //r = VMath.v3Add(r, VMath.v3ScalarMul(VMath.v3Cross(VMath.v3Neg(qimaginary), v), qw + qw));
        var cx = (qz * vy) - (qy * vz);
        var cy = (qx * vz) - (qz * vx);
        var cz = (qy * vx) - (qx * vy);
        var twoQw = qw + qw;
        rx += cx * twoQw;
        ry += cy * twoQw;
        rz += cz * twoQw;

        if (dst === undefined) {
            dst = new VMathArrayConstructor(3);
        }
        /* debug.assert(debug.isMathType(dst) && (debug.isVec3(dst) || debug.isVec4(dst))); */

        dst[0] = rx;
        dst[1] = ry;
        dst[2] = rz;

        return dst;
    },
    quatEqual: function quatEqual(q1, q2, precision) {
        if (precision === undefined) {
            precision = this.precision;
        }
        /* debug.assert(debug.isQuat(q1)); */
        /* debug.assert(debug.isQuat(q2)); */
        /* debug.assert(debug.isNumber(precision)); */

        var abs = Math.abs;
        return (abs(q1[0] - q2[0]) <= precision && abs(q1[1] - q2[1]) <= precision && abs(q1[2] - q2[2]) <= precision && abs(q1[3] - q2[3]) <= precision);
    },
    // quatPos
    quatPosBuild: function quatPosBuildFn(x, y, z, w, px, py, pz, dst) {
        var res;
        if (arguments.length < 7) {
            res = z;
            if (res === undefined) {
                res = new VMathArrayConstructor(7);
            }
            /* debug.assert(debug.isQuat(x)); */
            /* debug.assert(debug.isVec3(y) || debug.isVec4(y)); */
            /* debug.assert(debug.isMathType(res) && debug.isQuatPos(res)); */

            res[0] = x[0];
            res[1] = x[1];
            res[2] = x[2];
            res[3] = x[3];
            res[4] = y[0];
            res[5] = y[1];
            res[6] = y[2];
        } else {
            res = dst;
            if (res === undefined) {
                res = new VMathArrayConstructor(7);
            }
            /* debug.assert(debug.isMathType(res) && debug.isQuatPos(res)); */

            res[0] = x;
            res[1] = y;
            res[2] = z;
            res[3] = w;
            res[4] = px;
            res[5] = py;
            res[6] = pz;
        }
        return res;
    },
    quatPosTransformVector: function quatPosTransformVectorFn(qp, n, dst) {
        /* debug.assert(debug.isQuatPos(qp)); */
        return VMath.quatTransformVector(qp, n, dst);
    },
    quatPosTransformPoint: function quatPosTransformPointFn(qp, p) {
        /* debug.assert(debug.isQuatPos(qp)); */

        var offset = qp.slice(4, 7);

        var rotatedp = VMath.quatTransformVector(qp, p);
        return VMath.v3Add(rotatedp, offset);
    },
    quatPosMul: function quatPosMulFn(qp1, qp2) {
        /* debug.assert(debug.isQuatPos(qp1)); */
        /* debug.assert(debug.isQuatPos(qp2)); */

        var v2 = qp2.slice(4, 7);

        var qr = VMath.quatMul(qp1, qp2);
        var pr = VMath.quatPosTransformPoint(qp1, v2);
        qr[4] = pr[0];
        qr[5] = pr[1];
        qr[6] = pr[2];

        return qr;
    },
    //
    // Visibility queries
    //
    isVisibleBox: function isVisibleBoxFn(center, halfDimensions, vpm) {
        var abs = Math.abs;

        var c0 = center[0];
        var c1 = center[1];
        var c2 = center[2];

        var h0 = halfDimensions[0];
        var h1 = halfDimensions[1];
        var h2 = halfDimensions[2];

        var m0 = vpm[0];
        var m1 = vpm[1];
        var m2 = vpm[2];
        var m3 = vpm[3];
        var m4 = vpm[4];
        var m5 = vpm[5];
        var m6 = vpm[6];
        var m7 = vpm[7];
        var m8 = vpm[8];
        var m9 = vpm[9];
        var m10 = vpm[10];
        var m11 = vpm[11];

        var I0 = (m0 * h0);
        var I1 = (m1 * h0);
        var I2 = (m2 * h0);
        var I3 = (m3 * h0);
        var J0 = (m4 * h1);
        var J1 = (m5 * h1);
        var J2 = (m6 * h1);
        var J3 = (m7 * h1);
        var K0 = (m8 * h2);
        var K1 = (m9 * h2);
        var K2 = (m10 * h2);
        var K3 = (m11 * h2);

        var T0 = (m0 * c0 + m4 * c1 + m8 * c2 + vpm[12]);
        var T1 = (m1 * c0 + m5 * c1 + m9 * c2 + vpm[13]);
        var T2 = (m2 * c0 + m6 * c1 + m10 * c2 + vpm[14]);
        var T3 = (m3 * c0 + m7 * c1 + m11 * c2 + vpm[15]);

        return !(((T0 - T3) > (abs(I0 - I3) + abs(J0 - J3) + abs(K0 - K3))) || ((T0 + T3) < -(abs(I0 + I3) + abs(J0 + J3) + abs(K0 + K3))) || ((T1 - T3) > (abs(I1 - I3) + abs(J1 - J3) + abs(K1 - K3))) || ((T1 + T3) < -(abs(I1 + I3) + abs(J1 + J3) + abs(K1 + K3))) || ((T2 - T3) > (abs(I2 - I3) + abs(J2 - J3) + abs(K2 - K3))) || ((T2 + T3) < -(abs(I2 + I3) + abs(J2 + J3) + abs(K2 + K3))) || ((T3 + T3) < -(abs(I3 + I3) + abs(J3 + J3) + abs(K3 + K3))));
    },
    isVisibleBoxOrigin: function isVisibleBoxOriginFn(halfDimensions, vpm) {
        var abs = Math.abs;

        var h0 = halfDimensions[0];
        var h1 = halfDimensions[1];
        var h2 = halfDimensions[2];

        var I0 = (vpm[0] * h0);
        var I1 = (vpm[1] * h0);
        var I2 = (vpm[2] * h0);
        var I3 = (vpm[3] * h0);
        var J0 = (vpm[4] * h1);
        var J1 = (vpm[5] * h1);
        var J2 = (vpm[6] * h1);
        var J3 = (vpm[7] * h1);
        var K0 = (vpm[8] * h2);
        var K1 = (vpm[9] * h2);
        var K2 = (vpm[10] * h2);
        var K3 = (vpm[11] * h2);
        var T0 = vpm[12];
        var T1 = vpm[13];
        var T2 = vpm[14];
        var T3 = vpm[15];

        return !(((T0 - T3) > (abs(I0 - I3) + abs(J0 - J3) + abs(K0 - K3))) || ((T0 + T3) < -(abs(I0 + I3) + abs(J0 + J3) + abs(K0 + K3))) || ((T1 - T3) > (abs(I1 - I3) + abs(J1 - J3) + abs(K1 - K3))) || ((T1 + T3) < -(abs(I1 + I3) + abs(J1 + J3) + abs(K1 + K3))) || ((T2 - T3) > (abs(I2 - I3) + abs(J2 - J3) + abs(K2 - K3))) || ((T2 + T3) < -(abs(I2 + I3) + abs(J2 + J3) + abs(K2 + K3))) || ((T3 + T3) < -(abs(I3 + I3) + abs(J3 + J3) + abs(K3 + K3))));
    },
    isVisibleSphere: function isVisibleSphereFn(center, radius, vpm) {
        var abs = Math.abs;

        var c0 = center[0];
        var c1 = center[1];
        var c2 = center[2];

        var m0 = vpm[0];
        var m1 = vpm[1];
        var m2 = vpm[2];
        var m3 = vpm[3];
        var m4 = vpm[4];
        var m5 = vpm[5];
        var m6 = vpm[6];
        var m7 = vpm[7];
        var m8 = vpm[8];
        var m9 = vpm[9];
        var m10 = vpm[10];
        var m11 = vpm[11];

        var I0 = m0;
        var I1 = m1;
        var I2 = m2;
        var I3 = m3;
        var J0 = m4;
        var J1 = m5;
        var J2 = m6;
        var J3 = m7;
        var K0 = m8;
        var K1 = m9;
        var K2 = m10;
        var K3 = m11;

        var T0 = (m0 * c0 + m4 * c1 + m8 * c2 + vpm[12]);
        var T1 = (m1 * c0 + m5 * c1 + m9 * c2 + vpm[13]);
        var T2 = (m2 * c0 + m6 * c1 + m10 * c2 + vpm[14]);
        var T3 = (m3 * c0 + m7 * c1 + m11 * c2 + vpm[15]);

        var nradius = -radius;

        return !(((T0 - T3) > radius * (abs(I0 - I3) + abs(J0 - J3) + abs(K0 - K3))) || ((T0 + T3) < nradius * (abs(I0 + I3) + abs(J0 + J3) + abs(K0 + K3))) || ((T1 - T3) > radius * (abs(I1 - I3) + abs(J1 - J3) + abs(K1 - K3))) || ((T1 + T3) < nradius * (abs(I1 + I3) + abs(J1 + J3) + abs(K1 + K3))) || ((T2 - T3) > radius * (abs(I2 - I3) + abs(J2 - J3) + abs(K2 - K3))) || ((T2 + T3) < nradius * (abs(I2 + I3) + abs(J2 + J3) + abs(K2 + K3))) || ((T3 + T3) < nradius * (abs(I3 + I3) + abs(J3 + J3) + abs(K3 + K3))));
    },
    isVisibleSphereOrigin: function isVisibleSphereOriginFn(radius, vpm) {
        var abs = Math.abs;

        var I0 = vpm[0];
        var I1 = vpm[1];
        var I2 = vpm[2];
        var I3 = vpm[3];
        var J0 = vpm[4];
        var J1 = vpm[5];
        var J2 = vpm[6];
        var J3 = vpm[7];
        var K0 = vpm[8];
        var K1 = vpm[9];
        var K2 = vpm[10];
        var K3 = vpm[11];
        var T0 = vpm[12];
        var T1 = vpm[13];
        var T2 = vpm[14];
        var T3 = vpm[15];

        var nradius = -radius;

        return !(((T0 - T3) > radius * (abs(I0 - I3) + abs(J0 - J3) + abs(K0 - K3))) || ((T0 + T3) < nradius * (abs(I0 + I3) + abs(J0 + J3) + abs(K0 + K3))) || ((T1 - T3) > radius * (abs(I1 - I3) + abs(J1 - J3) + abs(K1 - K3))) || ((T1 + T3) < nradius * (abs(I1 + I3) + abs(J1 + J3) + abs(K1 + K3))) || ((T2 - T3) > radius * (abs(I2 - I3) + abs(J2 - J3) + abs(K2 - K3))) || ((T2 + T3) < nradius * (abs(I2 + I3) + abs(J2 + J3) + abs(K2 + K3))) || ((T3 + T3) < nradius * (abs(I3 + I3) + abs(J3 + J3) + abs(K3 + K3))));
    },
    isVisibleSphereUnit: function isVisibleSphereUnitFn(vpm) {
        var abs = Math.abs;

        var I0 = vpm[0];
        var I1 = vpm[1];
        var I2 = vpm[2];
        var I3 = vpm[3];
        var J0 = vpm[4];
        var J1 = vpm[5];
        var J2 = vpm[6];
        var J3 = vpm[7];
        var K0 = vpm[8];
        var K1 = vpm[9];
        var K2 = vpm[10];
        var K3 = vpm[11];
        var T0 = vpm[12];
        var T1 = vpm[13];
        var T2 = vpm[14];
        var T3 = vpm[15];

        return !(((T0 - T3) > (abs(I0 - I3) + abs(J0 - J3) + abs(K0 - K3))) || ((T0 + T3) < -(abs(I0 + I3) + abs(J0 + J3) + abs(K0 + K3))) || ((T1 - T3) > (abs(I1 - I3) + abs(J1 - J3) + abs(K1 - K3))) || ((T1 + T3) < -(abs(I1 + I3) + abs(J1 + J3) + abs(K1 + K3))) || ((T2 - T3) > (abs(I2 - I3) + abs(J2 - J3) + abs(K2 - K3))) || ((T2 + T3) < -(abs(I2 + I3) + abs(J2 + J3) + abs(K2 + K3))) || ((T3 + T3) < -(abs(I3 + I3) + abs(J3 + J3) + abs(K3 + K3))));
    },
    transformBox: function transformBoxFn(center, halfExtents, matrix) {
        var abs = Math.abs;
        var m0 = matrix[0];
        var m1 = matrix[1];
        var m2 = matrix[2];
        var m3 = matrix[3];
        var m4 = matrix[4];
        var m5 = matrix[5];
        var m6 = matrix[6];
        var m7 = matrix[7];
        var m8 = matrix[8];
        var c0 = center[0];
        var c1 = center[1];
        var c2 = center[2];
        var h0 = halfExtents[0];
        var h1 = halfExtents[1];
        var h2 = halfExtents[2];

        var out_center = new VMathArrayConstructor(3);
        out_center[0] = m0 * c0 + m3 * c1 + m6 * c2 + matrix[9];
        out_center[1] = m1 * c0 + m4 * c1 + m7 * c2 + matrix[10];
        out_center[2] = m2 * c0 + m5 * c1 + m8 * c2 + matrix[11];

        var out_halfext = new VMathArrayConstructor(3);
        out_halfext[0] = abs(m0) * h0 + abs(m3) * h1 + abs(m6) * h2;
        out_halfext[1] = abs(m1) * h0 + abs(m4) * h1 + abs(m7) * h2;
        out_halfext[2] = abs(m2) * h0 + abs(m5) * h1 + abs(m8) * h2;

        return {
            center: out_center,
            halfExtents: out_center
        };
    },
    //
    // Planes
    //
    planeNormalize: function planeNormalizeFn(plane, output) {
        if (output === undefined) {
            output = new VMathArrayConstructor(4);
        }

        var a = plane[0];
        var b = plane[1];
        var c = plane[2];
        var lsq = ((a * a) + (b * b) + (c * c));
        if (lsq > 0.0) {
            var lr = 1.0 / Math.sqrt(lsq);
            output[0] = (a * lr);
            output[1] = (b * lr);
            output[2] = (c * lr);
            output[3] = (plane[3] * lr);
        } else {
            output[0] = 0;
            output[1] = 0;
            output[2] = 0;
            output[3] = 0;
        }

        return output;
    },
    extractFrustumPlanes: function extractFrustumPlanesFn(m, p) {
        var planeNormalize = VMath.planeNormalize;
        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];
        var m12 = m[12];
        var m13 = m[13];
        var m14 = m[14];
        var m15 = m[15];
        var planes = (p || []);

        // Negate 'd' here to avoid doing it on the isVisible functions
        planes[0] = planeNormalize([
            (m3 + m0),
            (m7 + m4),
            (m11 + m8),
            -(m15 + m12)
        ], planes[0]);
        planes[1] = planeNormalize([
            (m3 - m0),
            (m7 - m4),
            (m11 - m8),
            -(m15 - m12)
        ], planes[1]);
        planes[2] = planeNormalize([
            (m3 - m1),
            (m7 - m5),
            (m11 - m9),
            -(m15 - m13)
        ], planes[2]);
        planes[3] = planeNormalize([
            (m3 + m1),
            (m7 + m5),
            (m11 + m9),
            -(m15 + m13)
        ], planes[3]);
        planes[4] = planeNormalize([
            (m3 + m2),
            (m7 + m6),
            (m11 + m10),
            -(m15 + m14)
        ], planes[4]);
        planes[5] = planeNormalize([
            (m3 - m2),
            (m7 - m6),
            (m11 - m10),
            -(m15 - m14)
        ], planes[5]);

        return planes;
    },
    isInsidePlanesPoint: function isInsidePlanesPointFn(p, planes) {
        var p0 = p[0];
        var p1 = p[1];
        var p2 = p[2];
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            if ((plane[0] * p0 + plane[1] * p1 + plane[2] * p2) < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    },
    isInsidePlanesSphere: function isInsidePlanesSphereFn(c, r, planes) {
        var c0 = c[0];
        var c1 = c[1];
        var c2 = c[2];
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            if ((plane[0] * c0 + plane[1] * c1 + plane[2] * c2) < (plane[3] - r)) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    },
    isInsidePlanesBox: function isInsidePlanesBoxFn(c, h, planes) {
        var c0 = c[0];
        var c1 = c[1];
        var c2 = c[2];
        var h0 = h[0];
        var h1 = h[1];
        var h2 = h[2];
        var p0 = (c0 + h0);
        var p1 = (c1 + h1);
        var p2 = (c2 + h2);
        var n0 = (c0 - h0);
        var n1 = (c1 - h1);
        var n2 = (c2 - h2);
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            if ((d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1) + d2 * (d2 < 0 ? n2 : p2)) < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    },
    extractIntersectingPlanes: function extractIntersectingPlanesFn(extents, planes) {
        var n0 = extents[0];
        var n1 = extents[1];
        var n2 = extents[2];
        var p0 = extents[3];
        var p1 = extents[4];
        var p2 = extents[5];
        var numPlanes = planes.length;
        var p = [];
        var np = 0;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            if ((d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1) + d2 * (d2 > 0 ? n2 : p2)) < plane[3]) {
                p[np] = plane;
                np += 1;
            }
            n += 1;
        } while(n < numPlanes);
        return p;
    }
};

if (typeof Float32Array !== "undefined") {
    var testVector = new Float32Array([1, 2, 3]);

    // Clamp FLOAT_MAX
    testVector[0] = VMath.FLOAT_MAX;

    VMath.FLOAT_MAX = testVector[0];
    VMathArrayConstructor = Float32Array;
}
VMath.arrayConstructor = VMathArrayConstructor;

if (TurbulenzEngine.hasOwnProperty('VMath')) {
    TurbulenzEngine.VMath = VMath;
}

// Copyright (c) 2010-2014 Turbulenz Limited
;
var Utilities = {
    //
    // assert
    //
    skipAsserts: false,
    assert: function assertFn(test, message) {
        if (!test) {
            if (!this.skipAsserts) {
                // Use a function that does not exist. This is caught in the debuggers.
                this.breakInDebugger.doesNotExist();
            }
        }
    },
    //
    // beget
    //
    beget: function begetFn(o) {
        /* tslint:disable:no-empty */
        var F = function () {
        };

        /* tslint:enable:no-empty */
        F.prototype = o;
        return new F();
    },
    //
    // log
    //
    log: function logFn(a, b) {
        var console = window.console;
        if (console) {
            switch (arguments.length) {
                case 1:
                    console.log(arguments[0]);
                    break;
                case 2:
                    console.log(arguments[0], arguments[1]);
                    break;
                case 3:
                    console.log(arguments[0], arguments[1], arguments[2]);
                    break;
                case 4:
                    console.log(arguments[0], arguments[1], arguments[2], arguments[3]);
                    break;
                default:
                    // Note: this will fail if using printf-style string formatting
                    var args = [].splice.call(arguments, 0);
                    console.log(args.join(' '));
                    break;
            }
        }
    },
    /* tslint:disable:no-bitwise */
    nearestLowerPow2: function UtilitiesNearestLowerPow2(num) {
        num = num | (num >>> 1);
        num = num | (num >>> 2);
        num = num | (num >>> 4);
        num = num | (num >>> 8);
        num = num | (num >>> 16);
        return (num - (num >>> 1));
    },
    nearestUpperPow2: function UtilitiesNearestUpperPow2(num) {
        num = num - 1;
        num = num | (num >>> 1);
        num = num | (num >>> 2);
        num = num | (num >>> 4);
        num = num | (num >>> 8);
        num = num | (num >>> 16);
        return (num + 1);
    },
    /* tslint:enable:no-bitwise */
    //
    // ajax
    //
    ajax: function utilitiesAjaxFn(params) {
        // parameters
        var requestText = "";
        var method = params.method;
        var data = params.data || {};
        var encrypted = params.encrypt;
        var signature = null;
        var url = params.url;
        var requestHandler = params.requestHandler;
        var callbackFn = params.callback;

        if (encrypted) {
            data.requestUrl = url;

            var str = JSON.stringify(data);

            if (method === "POST") {
                str = TurbulenzEngine.encrypt(str);
            }

            requestText += "data=" + encodeURIComponent(str) + "&";

            requestText += "gameSessionId=" + encodeURIComponent(data.gameSessionId);

            signature = TurbulenzEngine.generateSignature(str);
        } else if (data) {
            var key;
            for (key in data) {
                if (data.hasOwnProperty(key)) {
                    if (requestText.length !== 0) {
                        requestText += "&";
                    }
                    if (method === "POST") {
                        requestText += key + "=" + data[key];
                    } else {
                        requestText += encodeURIComponent(key) + "=" + encodeURIComponent(data[key]);
                    }
                }
            }
        }

        var httpResponseCallback = function httpResponseCallbackFn(xhrResponseText, xhrStatus) {
            // break circular reference
            var xhr = this.xhr;
            this.xhr.onreadystatechange = null;
            this.xhr = null;

            if (xhr.getResponseHeader("Content-Type") !== "application/json; charset=utf-8") {
                TurbulenzEngine.setTimeout(function () {
                    callbackFn({ msg: 'HttpStatus ' + xhrStatus + ' ' + Utilities.ajaxStatusCodes[xhrStatus] }, xhrStatus);
                    callbackFn = null;
                }, 0);
            } else {
                var response = JSON.parse(xhrResponseText);

                if (encrypted) {
                    var sig = xhr.getResponseHeader("X-TZ-Signature");
                    var validSignature = TurbulenzEngine.verifySignature(xhrResponseText, sig);
                    xhrResponseText = null;

                    TurbulenzEngine.setTimeout(function () {
                        var receivedUrl = response.requestUrl;

                        if (validSignature) {
                            if (!TurbulenzEngine.encryptionEnabled || receivedUrl === url) {
                                callbackFn(response, xhrStatus);
                                callbackFn = null;
                                return;
                            }
                        }

                        if (xhrStatus === 400) {
                            callbackFn(response, xhrStatus, "Verification Failed");
                        } else {
                            // Else drop reply
                            callbackFn({ msg: "Verification failed", ok: false }, 400, "Verification Failed");
                        }
                        callbackFn = null;
                    }, 0);
                } else {
                    xhrResponseText = null;

                    TurbulenzEngine.setTimeout(function () {
                        callbackFn(response, xhrStatus);
                        callbackFn = null;
                    }, 0);
                }
            }
        };

        var httpRequest = function httpRequestFn(url, onload, callContext) {
            var xhr;
            if (window.XMLHttpRequest) {
                xhr = new window.XMLHttpRequest();
            } else if (window.ActiveXObject) {
                xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
            } else {
                if (params.error) {
                    params.error("No XMLHTTPRequest object could be created");
                }
                return;
            }
            callContext.xhr = xhr;

            var httpCallback = function httpCallbackFn() {
                if (xhr.readyState === 4 && TurbulenzEngine && !TurbulenzEngine.isUnloading()) {
                    var xhrResponseText = xhr.responseText;
                    var xhrStatus = xhr.status;

                    // Checking xhrStatusText when xhrStatus is 0 causes a silent error!
                    var xhrStatusText = (xhrStatus !== 0 && xhr.statusText) || "No connection or cross domain request";

                    if (xhr.getAllResponseHeaders() === "" && xhrResponseText === "" && xhrStatus === 200 && xhrStatusText === 'OK') {
                        onload('', 0);
                        return;
                    }

                    onload.call(callContext, xhrResponseText, xhrStatus);
                }
            };

            // Send request
            xhr.open(method, ((requestText && (method !== "POST")) ? url + "?" + requestText : url), true);
            if (callbackFn) {
                xhr.onreadystatechange = httpCallback;
            }

            if (signature) {
                xhr.setRequestHeader("X-TZ-Signature", signature);
            }

            if (method === "POST") {
                xhr.setRequestHeader("Content-Type", "application/x-www-form-urlencoded; charset=UTF-8");
                xhr.send(requestText);
            } else {
                xhr.send();
            }
        };

        if (requestHandler) {
            requestHandler.request({
                src: url,
                requestFn: httpRequest,
                responseFilter: params.responseFilter,
                onload: httpResponseCallback
            });
        } else {
            var callContext = {
                src: url
            };
            httpRequest(url, httpResponseCallback, callContext);
        }
    },
    //
    // ajaxStatusCodes
    //
    // http://www.w3.org/Protocols/rfc2616/rfc2616-sec6.html#sec6.1
    ajaxStatusCodes: {
        0: "No Connection, Timeout Or Cross Domain Request",
        100: "Continue",
        101: "Switching Protocols",
        200: "OK",
        201: "Created",
        202: "Accepted",
        203: "Non-Authoritative Information",
        204: "No Content",
        205: "Reset Content",
        206: "Partial Content",
        300: "Multiple Choices",
        301: "Moved Permanently",
        302: "Found",
        303: "See Other",
        304: "Not Modified",
        305: "Use Proxy",
        307: "Temporary Redirect",
        400: "Bad Request",
        401: "Unauthorized",
        402: "Payment Required",
        403: "Forbidden",
        404: "Not Found",
        405: "Method Not Allowed",
        406: "Not Acceptable",
        407: "Proxy Authentication Required",
        408: "Request Time-out",
        409: "Conflict",
        410: "Gone",
        411: "Length Required",
        412: "Precondition Failed",
        413: "Request Entity Too Large",
        414: "Request-URI Too Large",
        415: "Unsupported Media Type",
        416: "Requested range not satisfiable",
        417: "Expectation Failed",
        429: "Too Many Requests",
        480: "Temporarily Unavailable",
        500: "Internal Server Error",
        501: "Not Implemented",
        502: "Bad Gateway",
        503: "Service Unavailable",
        504: "Gateway Time-out",
        505: "HTTP Version not supported"
    }
};

/* tslint:disable:no-unused-variable */
var MathDeviceConvert = /* tslint:enable:no-unused-variable */
{
    v2ToArray: function v2ToJavaScriptArrayFn(v2) {
        return [v2[0], v2[1]];
    },
    arrayToV2: function arrayToV2Fn(mathDevice, v2Array, v2Dest) {
        return mathDevice.v2Build(v2Array[0], v2Array[1], v2Dest);
    },
    v3ToArray: function v3ToJavaScriptArrayFn(v3) {
        return [v3[0], v3[1], v3[2]];
    },
    arrayToV3: function arrayToV3Fn(mathDevice, v3Array, v3Dest) {
        return mathDevice.v3Build(v3Array[0], v3Array[1], v3Array[2], v3Dest);
    },
    v4ToArray: function v4ToJavaScriptArrayFn(v4) {
        return [v4[0], v4[1], v4[2], v4[3]];
    },
    arrayToV4: function arrayToV4Fn(mathDevice, v4Array, v4Dest) {
        return mathDevice.v4Build(v4Array[0], v4Array[1], v4Array[2], v4Array[3], v4Dest);
    },
    quatToArray: function quatToJavaScriptArrayFn(quat) {
        return [quat[0], quat[1], quat[2], quat[3]];
    },
    arrayToQuat: function arrayToQuatFn(mathDevice, quatArray, quatDest) {
        return mathDevice.quatBuild(quatArray[0], quatArray[1], quatArray[2], quatArray[3], quatDest);
    },
    aabbToArray: function aabbToJavaScriptArrayFn(aabb) {
        return [
            aabb[0],
            aabb[1],
            aabb[2],
            aabb[3],
            aabb[4],
            aabb[5]
        ];
    },
    arrayToAABB: function arrayToQuatFn(mathDevice, aabbArray, aabbDest) {
        return mathDevice.aabbBuild(aabbArray[0], aabbArray[1], aabbArray[2], aabbArray[3], aabbArray[4], aabbArray[5], aabbDest);
    },
    quatPosToArray: function quatPosToJavaScriptArrayFn(quatPos) {
        return [
            quatPos[0],
            quatPos[1],
            quatPos[2],
            quatPos[3],
            quatPos[4],
            quatPos[5],
            quatPos[6]
        ];
    },
    arrayToQuatPos: function arrayToQuatPosFn(mathDevice, quatPosArray, quatPosDest) {
        return mathDevice.quatPosBuild(quatPosArray[0], quatPosArray[1], quatPosArray[2], quatPosArray[3], quatPosArray[4], quatPosArray[5], quatPosArray[6], quatPosDest);
    },
    m33ToArray: function m33ToJavaScriptArrayFn(m33) {
        return [
            m33[0],
            m33[1],
            m33[2],
            m33[3],
            m33[4],
            m33[5],
            m33[6],
            m33[7],
            m33[8]
        ];
    },
    arrayToM33: function arrayToM33Fn(mathDevice, m33Array, m33Dest) {
        return mathDevice.m33Build(m33Array[0], m33Array[1], m33Array[2], m33Array[3], m33Array[4], m33Array[5], m33Array[6], m33Array[7], m33Array[8], m33Dest);
    },
    m43ToArray: function m43ToJavaScriptArrayFn(m43) {
        return [
            m43[0],
            m43[1],
            m43[2],
            m43[3],
            m43[4],
            m43[5],
            m43[6],
            m43[7],
            m43[8],
            m43[9],
            m43[10],
            m43[11]
        ];
    },
    arrayToM43: function arrayToM43Fn(mathDevice, m43Array, m43Dest) {
        return mathDevice.m43Build(m43Array[0], m43Array[1], m43Array[2], m43Array[3], m43Array[4], m43Array[5], m43Array[6], m43Array[7], m43Array[8], m43Array[9], m43Array[10], m43Array[11], m43Dest);
    },
    m34ToArray: function m34ToJavaScriptArrayFn(m34) {
        return [
            m34[0],
            m34[1],
            m34[2],
            m34[3],
            m34[4],
            m34[5],
            m34[6],
            m34[7],
            m34[8],
            m34[9],
            m34[10],
            m34[11]
        ];
    },
    m44ToArray: function m44ToJavaScriptArrayFn(m44) {
        return [
            m44[0],
            m44[1],
            m44[2],
            m44[3],
            m44[4],
            m44[5],
            m44[6],
            m44[7],
            m44[8],
            m44[9],
            m44[10],
            m44[11],
            m44[12],
            m44[13],
            m44[14],
            m44[15]
        ];
    },
    arrayToM44: function arrayToM44Fn(mathDevice, m44Array, m44Dest) {
        return mathDevice.m44Build(m44Array[0], m44Array[1], m44Array[2], m44Array[3], m44Array[4], m44Array[5], m44Array[6], m44Array[7], m44Array[8], m44Array[9], m44Array[10], m44Array[11], m44Array[12], m44Array[13], m44Array[14], m44Array[15], m44Dest);
    }
};

//
//Reference
//
// Proxy reference class allowing weak reference to the object
var Reference = (function () {
    function Reference() {
    }
    //
    // add
    //
    Reference.prototype.add = function () {
        this.referenceCount += 1;
    };

    //
    // remove
    //
    Reference.prototype.remove = function () {
        this.referenceCount -= 1;
        if (this.referenceCount === 0) {
            if (this.destroyedObserver) {
                this.destroyedObserver.notify(this.object);
            }
            this.object.destroy();
            this.object = null;
        }
    };

    //
    //subscribeDestroyed
    //
    Reference.prototype.subscribeDestroyed = function (observerFunction) {
        if (!this.destroyedObserver) {
            this.destroyedObserver = Observer.create();
        }
        this.destroyedObserver.subscribe(observerFunction);
    };

    //
    //unsubscribeDestroyed
    //
    Reference.prototype.unsubscribeDestroyed = function (observerFunction) {
        this.destroyedObserver.unsubscribe(observerFunction);
    };

    Reference.create = //
    // create
    //
    function (object) {
        var result = new Reference();
        result.object = object;
        result.referenceCount = 0;
        return result;
    };
    Reference.version = 1;
    return Reference;
})();

//
// Profile
//
var Profile = {
    profiles: {},
    sortMode: { alphabetical: 0, duration: 1, max: 2, min: 3, calls: 4 },
    //
    // start
    //
    start: function profileStartFn(name) {
        var data = this.profiles[name];
        if (!data) {
            data = { name: name, calls: 0, duration: 0.0, min: Number.MAX_VALUE, max: 0.0, sumOfSquares: 0.0 };
            this.profiles[name] = data;
        }
        data.start = TurbulenzEngine.time;
    },
    //
    // stop
    //
    stop: function profileStopFn(name) {
        var end = TurbulenzEngine.time;
        var data = this.profiles[name];
        if (data) {
            var duration = end - data.start;
            data.duration += duration;
            data.calls += 1;
            data.sumOfSquares += duration * duration;

            if (duration > data.max) {
                data.max = duration;
            }

            if (duration < data.min) {
                data.min = duration;
            }
        }
    },
    //
    // reset
    //
    reset: function profileResetFn() {
        this.profiles = {};
    },
    //
    // getReport
    //
    getReport: function profileGetReportFn(sortMode, format) {
        var dataArray = [];
        var data;
        var maxDuration = 0.0;
        var name;
        for (name in this.profiles) {
            if (this.profiles.hasOwnProperty(name)) {
                data = this.profiles[name];
                if (maxDuration < data.duration) {
                    maxDuration = data.duration;
                }
                dataArray.push(data);
            }
        }

        var compareFunction;

        if (sortMode === Profile.sortMode.alphabetical) {
            compareFunction = function compareName(left, right) {
                return (left.name < right.name) ? -1 : (left.name > right.name) ? 1 : 0;
            };
        } else if (sortMode === Profile.sortMode.max) {
            compareFunction = function compareMax(left, right) {
                return right.max - left.max;
            };
        } else if (sortMode === Profile.sortMode.min) {
            compareFunction = function compareMin(left, right) {
                return right.min - left.min;
            };
        } else if (sortMode === Profile.sortMode.calls) {
            compareFunction = function compareCalls(left, right) {
                return right.calls - left.calls;
            };
        } else {
            compareFunction = function compareDuration(left, right) {
                return right.duration - left.duration;
            };
        }

        dataArray.sort(compareFunction);

        var line;
        var text = "";
        var precision = format ? format.precision : 8;
        var percentagePrecision = format ? format.percentagePrecision : 1;
        var seperator = format ? format.seperator : " ";
        var length = dataArray.length;
        var standardDeviation;
        var mean;
        var index;
        for (index = 0; index < length; index += 1) {
            data = dataArray[index];
            line = data.name;
            line += seperator + data.calls;
            line += seperator + data.duration.toFixed(precision);
            line += seperator + data.max.toFixed(precision);
            line += seperator + data.min.toFixed(precision);
            mean = data.duration / data.calls;
            line += seperator + mean.toFixed(precision);
            standardDeviation = Math.sqrt(data.sumOfSquares / data.calls - mean * mean);
            line += seperator + standardDeviation.toFixed(precision);
            line += seperator + (100 * data.duration / maxDuration).toFixed(percentagePrecision) + "%\n";
            text += line;
        }
        return text;
    }
};

;

var JSProfiling = {};

//
// createArray
//      Creates an array of nodes by merging all duplicate function references in the call profile tree together.
JSProfiling.createArray = function JSProfilingCreateArrayFn(rootNode) {
    var map = {};
    var array = [];

    if (rootNode.head) {
        rootNode = rootNode.head;
    }

    var processNode = function processNodeFn(node) {
        var urlObject = map[node.url];
        if (!urlObject) {
            urlObject = {};
            map[node.url] = urlObject;
        }

        var functionName = node.functionName === "" ? "(anonymous)" : node.functionName;

        var functionObject = urlObject[functionName];
        if (!functionObject) {
            functionObject = {};
            urlObject[functionName] = functionObject;
        }

        var existingNode = functionObject[node.lineNumber];
        if (!existingNode) {
            var newNode = {
                functionName: functionName,
                numberOfCalls: node.numberOfCalls,
                totalTime: node.totalTime,
                selfTime: node.selfTime,
                url: node.url,
                lineNumber: node.lineNumber
            };

            array[array.length] = newNode;
            functionObject[node.lineNumber] = newNode;
        } else {
            existingNode.totalTime += node.totalTime;
            existingNode.selfTime += node.selfTime;
            existingNode.numberOfCalls += node.numberOfCalls;
        }

        var children = typeof node.children === 'function' ? node.children() : node.children;
        if (children) {
            var numberOfChildren = children.length;
            var childIndex;
            for (childIndex = 0; childIndex < numberOfChildren; childIndex += 1) {
                processNode(children[childIndex]);
            }
        }
    };

    processNode(rootNode);

    return array;
};

//
// sort
//
JSProfiling.sort = function JSProfilingSortFn(array, propertyName, descending) {
    if (!propertyName) {
        propertyName = "totalTime";
    }

    var sorterAscending = function (left, right) {
        return left[propertyName] - right[propertyName];
    };

    var sorterDescending = function (left, right) {
        return right[propertyName] - left[propertyName];
    };

    if (descending === false) {
        array.sort(sorterAscending);
    } else {
        array.sort(sorterDescending);
    }
};

// Copyright (c) 2009-2014 Turbulenz Limited
/*global Float32Array: false*/
;

//
// AABBTreeNode
//
var AABBTreeNode = (function () {
    function AABBTreeNode(extents, escapeNodeOffset, externalNode) {
        this.escapeNodeOffset = escapeNodeOffset;
        this.externalNode = externalNode;
        this.extents = extents;

        return this;
    }
    AABBTreeNode.prototype.isLeaf = function () {
        return !!this.externalNode;
    };

    AABBTreeNode.prototype.reset = function (minX, minY, minZ, maxX, maxY, maxZ, escapeNodeOffset, externalNode) {
        this.escapeNodeOffset = escapeNodeOffset;
        this.externalNode = externalNode;
        var oldExtents = this.extents;
        oldExtents[0] = minX;
        oldExtents[1] = minY;
        oldExtents[2] = minZ;
        oldExtents[3] = maxX;
        oldExtents[4] = maxY;
        oldExtents[5] = maxZ;
    };

    AABBTreeNode.prototype.clear = function () {
        this.escapeNodeOffset = 1;
        this.externalNode = undefined;
        var oldExtents = this.extents;
        var maxNumber = Number.MAX_VALUE;
        oldExtents[0] = maxNumber;
        oldExtents[1] = maxNumber;
        oldExtents[2] = maxNumber;
        oldExtents[3] = -maxNumber;
        oldExtents[4] = -maxNumber;
        oldExtents[5] = -maxNumber;
    };

    AABBTreeNode.create = // Constructor function
    function (extents, escapeNodeOffset, externalNode) {
        return new AABBTreeNode(extents, escapeNodeOffset, externalNode);
    };
    AABBTreeNode.version = 1;
    return AABBTreeNode;
})();

//
// AABBTree
//
var AABBTree = (function () {
    function AABBTree(highQuality) {
        this.numNodesLeaf = 4;
        this.nodes = [];
        this.endNode = 0;
        this.needsRebuild = false;
        this.needsRebound = false;
        this.numAdds = 0;
        this.numUpdates = 0;
        this.numExternalNodes = 0;
        this.startUpdate = 0x7FFFFFFF;
        this.endUpdate = -0x7FFFFFFF;
        this.highQuality = highQuality;
        this.ignoreY = false;
        this.nodesStack = new Array(32);
    }
    AABBTree.allocateNode = function () {
        var nodesPool = this.nodesPool;
        if (!nodesPool.length) {
            // Allocate a bunch of nodes in one go
            var nodesPoolAllocationSize = this.nodesPoolAllocationSize;
            var useFloat32Array = this.useFloat32Array;
            var extentsArray, extentsArrayIndex;
            if (useFloat32Array) {
                extentsArray = new Float32Array(nodesPoolAllocationSize * 6);
                extentsArrayIndex = 0;
            }
            var n, extents;
            for (n = 0; n < nodesPoolAllocationSize; n += 1) {
                if (useFloat32Array) {
                    extents = extentsArray.subarray(extentsArrayIndex, (extentsArrayIndex + 6));
                    extentsArrayIndex += 6;
                } else {
                    extents = [0, 0, 0, 0, 0, 0];
                }
                nodesPool[n] = AABBTreeNode.create(extents, 1, undefined);
            }
        }
        return nodesPool.pop();
    };

    AABBTree.releaseNode = function (node) {
        var nodesPool = this.nodesPool;
        if (nodesPool.length < this.nodesPoolAllocationSize) {
            node.clear();
            nodesPool.push(node);
        }
    };

    AABBTree.recycleNodes = function (nodes, start) {
        var numNodes = nodes.length;
        var n;
        for (n = start; n < numNodes; n += 1) {
            var node = nodes[n];
            if (node) {
                this.releaseNode(node);
            }
        }
        nodes.length = start;
    };

    AABBTree.prototype.add = function (externalNode, extents) {
        var endNode = this.endNode;
        externalNode.spatialIndex = endNode;

        var node = AABBTree.allocateNode();
        node.escapeNodeOffset = 1;
        node.externalNode = externalNode;
        var copyExtents = node.extents;
        copyExtents[0] = extents[0];
        copyExtents[1] = extents[1];
        copyExtents[2] = extents[2];
        copyExtents[3] = extents[3];
        copyExtents[4] = extents[4];
        copyExtents[5] = extents[5];

        this.nodes[endNode] = node;
        this.endNode = (endNode + 1);
        this.needsRebuild = true;
        this.numAdds += 1;
        this.numExternalNodes += 1;
    };

    AABBTree.prototype.remove = function (externalNode) {
        var index = externalNode.spatialIndex;
        if (index !== undefined) {
            if (this.numExternalNodes > 1) {
                var nodes = this.nodes;

                nodes[index].clear();

                var endNode = this.endNode;
                if ((index + 1) >= endNode) {
                    while (!nodes[endNode - 1].externalNode) {
                        endNode -= 1;
                    }
                    this.endNode = endNode;
                } else {
                    this.needsRebuild = true;
                }
                this.numExternalNodes -= 1;
            } else {
                this.clear();
            }

            externalNode.spatialIndex = undefined;
        }
    };

    AABBTree.prototype.findParent = function (nodeIndex) {
        var nodes = this.nodes;
        var parentIndex = nodeIndex;
        var nodeDist = 0;
        var parent;
        do {
            parentIndex -= 1;
            nodeDist += 1;
            parent = nodes[parentIndex];
        } while(parent.escapeNodeOffset <= nodeDist);
        return parent;
    };

    AABBTree.prototype.update = function (externalNode, extents) {
        var index = externalNode.spatialIndex;
        if (index !== undefined) {
            var min0 = extents[0];
            var min1 = extents[1];
            var min2 = extents[2];
            var max0 = extents[3];
            var max1 = extents[4];
            var max2 = extents[5];

            var needsRebuild = this.needsRebuild;
            var needsRebound = this.needsRebound;
            var nodes = this.nodes;
            var node = nodes[index];
            var nodeExtents = node.extents;

            var doUpdate = (needsRebuild || needsRebound || nodeExtents[0] > min0 || nodeExtents[1] > min1 || nodeExtents[2] > min2 || nodeExtents[3] < max0 || nodeExtents[4] < max1 || nodeExtents[5] < max2);

            nodeExtents[0] = min0;
            nodeExtents[1] = min1;
            nodeExtents[2] = min2;
            nodeExtents[3] = max0;
            nodeExtents[4] = max1;
            nodeExtents[5] = max2;

            if (doUpdate) {
                if (!needsRebuild && 1 < nodes.length) {
                    this.numUpdates += 1;
                    if (this.startUpdate > index) {
                        this.startUpdate = index;
                    }
                    if (this.endUpdate < index) {
                        this.endUpdate = index;
                    }
                    if (!needsRebound) {
                        if ((2 * this.numUpdates) > this.numExternalNodes) {
                            this.needsRebound = true;
                        } else {
                            var parent = this.findParent(index);
                            var parentExtents = parent.extents;
                            if (parentExtents[0] > min0 || parentExtents[1] > min1 || parentExtents[2] > min2 || parentExtents[3] < max0 || parentExtents[4] < max1 || parentExtents[5] < max2) {
                                this.needsRebound = true;
                            }
                        }
                    } else {
                        if (this.numUpdates > (3 * this.numExternalNodes)) {
                            this.needsRebuild = true;
                            this.numAdds = this.numUpdates;
                        }
                    }
                }
            }
        } else {
            this.add(externalNode, extents);
        }
    };

    AABBTree.prototype.needsFinalize = function () {
        return (this.needsRebuild || this.needsRebound);
    };

    AABBTree.prototype.finalize = function () {
        if (this.needsRebuild) {
            this.rebuild();
        } else if (this.needsRebound) {
            this.rebound();
        }
    };

    AABBTree.prototype.rebound = function () {
        var nodes = this.nodes;
        if (nodes.length > 1) {
            var startUpdateNodeIndex = this.startUpdate;
            var endUpdateNodeIndex = this.endUpdate;

            var nodesStack = this.nodesStack;
            var numNodesStack = 0;
            var topNodeIndex = 0;
            for (; ;) {
                var topNode = nodes[topNodeIndex];
                var currentNodeIndex = topNodeIndex;
                var currentEscapeNodeIndex = (topNodeIndex + topNode.escapeNodeOffset);
                var nodeIndex = (topNodeIndex + 1);
                var node;
                do {
                    node = nodes[nodeIndex];
                    var escapeNodeIndex = (nodeIndex + node.escapeNodeOffset);
                    if (nodeIndex < endUpdateNodeIndex) {
                        if (!node.externalNode) {
                            if (escapeNodeIndex > startUpdateNodeIndex) {
                                nodesStack[numNodesStack] = topNodeIndex;
                                numNodesStack += 1;
                                topNodeIndex = nodeIndex;
                            }
                        }
                    } else {
                        break;
                    }
                    nodeIndex = escapeNodeIndex;
                } while(nodeIndex < currentEscapeNodeIndex);

                if (topNodeIndex === currentNodeIndex) {
                    nodeIndex = (topNodeIndex + 1);
                    node = nodes[nodeIndex];

                    var extents = node.extents;
                    var minX = extents[0];
                    var minY = extents[1];
                    var minZ = extents[2];
                    var maxX = extents[3];
                    var maxY = extents[4];
                    var maxZ = extents[5];

                    nodeIndex = (nodeIndex + node.escapeNodeOffset);
                    while (nodeIndex < currentEscapeNodeIndex) {
                        node = nodes[nodeIndex];
                        extents = node.extents;

                        if (minX > extents[0]) {
                            minX = extents[0];
                        }
                        if (minY > extents[1]) {
                            minY = extents[1];
                        }
                        if (minZ > extents[2]) {
                            minZ = extents[2];
                        }
                        if (maxX < extents[3]) {
                            maxX = extents[3];
                        }
                        if (maxY < extents[4]) {
                            maxY = extents[4];
                        }
                        if (maxZ < extents[5]) {
                            maxZ = extents[5];
                        }

                        /*jshint white: true*/
                        nodeIndex = (nodeIndex + node.escapeNodeOffset);
                    }

                    extents = topNode.extents;
                    extents[0] = minX;
                    extents[1] = minY;
                    extents[2] = minZ;
                    extents[3] = maxX;
                    extents[4] = maxY;
                    extents[5] = maxZ;

                    endUpdateNodeIndex = topNodeIndex;

                    if (0 < numNodesStack) {
                        numNodesStack -= 1;
                        topNodeIndex = nodesStack[numNodesStack];
                    } else {
                        break;
                    }
                }
            }
        }

        this.needsRebuild = false;
        this.needsRebound = false;
        this.numAdds = 0;

        //this.numUpdates = 0;
        this.startUpdate = 0x7FFFFFFF;
        this.endUpdate = -0x7FFFFFFF;
    };

    AABBTree.prototype.rebuild = function () {
        if (this.numExternalNodes > 0) {
            var nodes = this.nodes;

            var n, buildNodes, numBuildNodes, endNodeIndex;

            if (this.numExternalNodes === nodes.length) {
                buildNodes = nodes;
                numBuildNodes = nodes.length;
                nodes = [];
                this.nodes = nodes;
            } else {
                buildNodes = [];
                buildNodes.length = this.numExternalNodes;
                numBuildNodes = 0;
                endNodeIndex = this.endNode;
                for (n = 0; n < endNodeIndex; n += 1) {
                    var currentNode = nodes[n];
                    if (currentNode.externalNode) {
                        nodes[n] = undefined;
                        buildNodes[numBuildNodes] = currentNode;
                        numBuildNodes += 1;
                    }
                }
                if (buildNodes.length > numBuildNodes) {
                    buildNodes.length = numBuildNodes;
                }
            }

            var rootNode;
            if (numBuildNodes > 1) {
                if (numBuildNodes > this.numNodesLeaf && this.numAdds > 0) {
                    if (this.highQuality) {
                        this._sortNodesHighQuality(buildNodes);
                    } else if (this.ignoreY) {
                        this._sortNodesNoY(buildNodes);
                    } else {
                        this._sortNodes(buildNodes);
                    }
                }

                var predictedNumNodes = this._predictNumNodes(0, numBuildNodes, 0);
                if (nodes.length > predictedNumNodes) {
                    AABBTree.recycleNodes(nodes, predictedNumNodes);
                }

                this._recursiveBuild(buildNodes, 0, numBuildNodes, 0);

                endNodeIndex = nodes[0].escapeNodeOffset;
                if (nodes.length > endNodeIndex) {
                    AABBTree.recycleNodes(nodes, endNodeIndex);
                }
                this.endNode = endNodeIndex;

                // Check if we should take into account the Y coordinate
                rootNode = nodes[0];
                var extents = rootNode.extents;
                var deltaX = (extents[3] - extents[0]);
                var deltaY = (extents[4] - extents[1]);
                var deltaZ = (extents[5] - extents[2]);
                this.ignoreY = ((4 * deltaY) < (deltaX <= deltaZ ? deltaX : deltaZ));
            } else {
                rootNode = buildNodes[0];
                rootNode.externalNode.spatialIndex = 0;
                nodes.length = 1;
                nodes[0] = rootNode;
                this.endNode = 1;
            }
            buildNodes = null;
        }

        this.needsRebuild = false;
        this.needsRebound = false;
        this.numAdds = 0;
        this.numUpdates = 0;
        this.startUpdate = 0x7FFFFFFF;
        this.endUpdate = -0x7FFFFFFF;
    };

    AABBTree.prototype._sortNodes = function (nodes) {
        var numNodesLeaf = this.numNodesLeaf;
        var numNodes = nodes.length;

        function getkeyXfn(node) {
            var extents = node.extents;
            return (extents[0] + extents[3]);
        }

        function getkeyYfn(node) {
            var extents = node.extents;
            return (extents[1] + extents[4]);
        }

        function getkeyZfn(node) {
            var extents = node.extents;
            return (extents[2] + extents[5]);
        }

        function getreversekeyXfn(node) {
            var extents = node.extents;
            return -(extents[0] + extents[3]);
        }

        function getreversekeyYfn(node) {
            var extents = node.extents;
            return -(extents[1] + extents[4]);
        }

        function getreversekeyZfn(node) {
            var extents = node.extents;
            return -(extents[2] + extents[5]);
        }

        var nthElement = this._nthElement;
        var reverse = false;
        var axis = 0;

        function sortNodesRecursive(nodes, startIndex, endIndex) {
            /* tslint:disable:no-bitwise */
            var splitNodeIndex = ((startIndex + endIndex) >> 1);

            if (axis === 0) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
                }
            } else if (axis === 2) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn);
                }
            } else {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
                }
            }

            if (axis === 0) {
                axis = 2;
            } else if (axis === 2) {
                axis = 1;
            } else {
                axis = 0;
            }

            reverse = !reverse;

            if ((startIndex + numNodesLeaf) < splitNodeIndex) {
                sortNodesRecursive(nodes, startIndex, splitNodeIndex);
            }

            if ((splitNodeIndex + numNodesLeaf) < endIndex) {
                sortNodesRecursive(nodes, splitNodeIndex, endIndex);
            }
        }

        sortNodesRecursive(nodes, 0, numNodes);
    };

    AABBTree.prototype._sortNodesNoY = function (nodes) {
        var numNodesLeaf = this.numNodesLeaf;
        var numNodes = nodes.length;

        function getkeyXfn(node) {
            var extents = node.extents;
            return (extents[0] + extents[3]);
        }

        function getkeyZfn(node) {
            var extents = node.extents;
            return (extents[2] + extents[5]);
        }

        function getreversekeyXfn(node) {
            var extents = node.extents;
            return -(extents[0] + extents[3]);
        }

        function getreversekeyZfn(node) {
            var extents = node.extents;
            return -(extents[2] + extents[5]);
        }

        var nthElement = this._nthElement;
        var reverse = false;
        var axis = 0;

        function sortNodesNoYRecursive(nodes, startIndex, endIndex) {
            /* tslint:disable:no-bitwise */
            var splitNodeIndex = ((startIndex + endIndex) >> 1);

            if (axis === 0) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
                }
            } else {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn);
                }
            }

            if (axis === 0) {
                axis = 2;
            } else {
                axis = 0;
            }

            reverse = !reverse;

            if ((startIndex + numNodesLeaf) < splitNodeIndex) {
                sortNodesNoYRecursive(nodes, startIndex, splitNodeIndex);
            }

            if ((splitNodeIndex + numNodesLeaf) < endIndex) {
                sortNodesNoYRecursive(nodes, splitNodeIndex, endIndex);
            }
        }

        sortNodesNoYRecursive(nodes, 0, numNodes);
    };

    AABBTree.prototype._sortNodesHighQuality = function (nodes) {
        var numNodesLeaf = this.numNodesLeaf;
        var numNodes = nodes.length;

        function getkeyXfn(node) {
            var extents = node.extents;
            return (extents[0] + extents[3]);
        }

        function getkeyYfn(node) {
            var extents = node.extents;
            return (extents[1] + extents[4]);
        }

        function getkeyZfn(node) {
            var extents = node.extents;
            return (extents[2] + extents[5]);
        }

        function getkeyXZfn(node) {
            var extents = node.extents;
            return (extents[0] + extents[2] + extents[3] + extents[5]);
        }

        function getkeyZXfn(node) {
            var extents = node.extents;
            return (extents[0] - extents[2] + extents[3] - extents[5]);
        }

        function getreversekeyXfn(node) {
            var extents = node.extents;
            return -(extents[0] + extents[3]);
        }

        function getreversekeyYfn(node) {
            var extents = node.extents;
            return -(extents[1] + extents[4]);
        }

        function getreversekeyZfn(node) {
            var extents = node.extents;
            return -(extents[2] + extents[5]);
        }

        function getreversekeyXZfn(node) {
            var extents = node.extents;
            return -(extents[0] + extents[2] + extents[3] + extents[5]);
        }

        function getreversekeyZXfn(node) {
            var extents = node.extents;
            return -(extents[0] - extents[2] + extents[3] - extents[5]);
        }

        var nthElement = this._nthElement;
        var calculateSAH = this._calculateSAH;
        var reverse = false;

        function sortNodesHighQualityRecursive(nodes, startIndex, endIndex) {
            /* tslint:disable:no-bitwise */
            var splitNodeIndex = ((startIndex + endIndex) >> 1);

            /* tslint:enable:no-bitwise */
            nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
            var sahX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));

            nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
            var sahY = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));

            nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn);
            var sahZ = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));

            nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXZfn);
            var sahXZ = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));

            nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZXfn);
            var sahZX = (calculateSAH(nodes, startIndex, splitNodeIndex) + calculateSAH(nodes, splitNodeIndex, endIndex));

            if (sahX <= sahY && sahX <= sahZ && sahX <= sahXZ && sahX <= sahZX) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXfn);
                }
            } else if (sahZ <= sahY && sahZ <= sahXZ && sahZ <= sahZX) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZfn);
                }
            } else if (sahY <= sahXZ && sahY <= sahZX) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyYfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyYfn);
                }
            } else if (sahXZ <= sahZX) {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyXZfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyXZfn);
                }
            } else {
                if (reverse) {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getreversekeyZXfn);
                } else {
                    nthElement(nodes, startIndex, splitNodeIndex, endIndex, getkeyZXfn);
                }
            }

            reverse = !reverse;

            if ((startIndex + numNodesLeaf) < splitNodeIndex) {
                sortNodesHighQualityRecursive(nodes, startIndex, splitNodeIndex);
            }

            if ((splitNodeIndex + numNodesLeaf) < endIndex) {
                sortNodesHighQualityRecursive(nodes, splitNodeIndex, endIndex);
            }
        }

        sortNodesHighQualityRecursive(nodes, 0, numNodes);
    };

    AABBTree.prototype._calculateSAH = function (buildNodes, startIndex, endIndex) {
        var buildNode, extents, minX, minY, minZ, maxX, maxY, maxZ;

        buildNode = buildNodes[startIndex];
        extents = buildNode.extents;
        minX = extents[0];
        minY = extents[1];
        minZ = extents[2];
        maxX = extents[3];
        maxY = extents[4];
        maxZ = extents[5];

        for (var n = (startIndex + 1); n < endIndex; n += 1) {
            buildNode = buildNodes[n];
            extents = buildNode.extents;

            if (minX > extents[0]) {
                minX = extents[0];
            }
            if (minY > extents[1]) {
                minY = extents[1];
            }
            if (minZ > extents[2]) {
                minZ = extents[2];
            }
            if (maxX < extents[3]) {
                maxX = extents[3];
            }
            if (maxY < extents[4]) {
                maxY = extents[4];
            }
            if (maxZ < extents[5]) {
                maxZ = extents[5];
            }
            /*jshint white: true*/
        }

        return ((maxX - minX) + (maxY - minY) + (maxZ - minZ));
    };

    AABBTree.prototype._nthElement = function (nodes, first, nth, last, getkey) {
        function medianFn(a, b, c) {
            if (a < b) {
                if (b < c) {
                    return b;
                } else if (a < c) {
                    return c;
                } else {
                    return a;
                }
            } else if (a < c) {
                return a;
            } else if (b < c) {
                return c;
            }
            return b;
        }

        function insertionSortFn(nodes, first, last, getkey) {
            var sorted = (first + 1);
            while (sorted !== last) {
                var tempNode = nodes[sorted];
                var tempKey = getkey(tempNode);

                var next = sorted;
                var current = (sorted - 1);

                while (next !== first && tempKey < getkey(nodes[current])) {
                    nodes[next] = nodes[current];
                    next -= 1;
                    current -= 1;
                }

                if (next !== sorted) {
                    nodes[next] = tempNode;
                }

                sorted += 1;
            }
        }

        while ((last - first) > 8) {
            /* tslint:disable:no-bitwise */
            var midValue = medianFn(getkey(nodes[first]), getkey(nodes[first + ((last - first) >> 1)]), getkey(nodes[last - 1]));

            /* tslint:enable:no-bitwise */
            var firstPos = first;
            var lastPos = last;
            var midPos;
            for (; ; firstPos += 1) {
                while (getkey(nodes[firstPos]) < midValue) {
                    firstPos += 1;
                }

                do {
                    lastPos -= 1;
                } while(midValue < getkey(nodes[lastPos]));

                if (firstPos >= lastPos) {
                    midPos = firstPos;
                    break;
                } else {
                    var temp = nodes[firstPos];
                    nodes[firstPos] = nodes[lastPos];
                    nodes[lastPos] = temp;
                }
            }

            if (midPos <= nth) {
                first = midPos;
            } else {
                last = midPos;
            }
        }

        insertionSortFn(nodes, first, last, getkey);
    };

    AABBTree.prototype._recursiveBuild = function (buildNodes, startIndex, endIndex, lastNodeIndex) {
        var nodes = this.nodes;
        var nodeIndex = lastNodeIndex;
        lastNodeIndex += 1;

        var minX, minY, minZ, maxX, maxY, maxZ, extents;
        var buildNode, lastNode;

        if ((startIndex + this.numNodesLeaf) >= endIndex) {
            buildNode = buildNodes[startIndex];
            extents = buildNode.extents;
            minX = extents[0];
            minY = extents[1];
            minZ = extents[2];
            maxX = extents[3];
            maxY = extents[4];
            maxZ = extents[5];

            buildNode.externalNode.spatialIndex = lastNodeIndex;
            this._replaceNode(nodes, lastNodeIndex, buildNode);

            for (var n = (startIndex + 1); n < endIndex; n += 1) {
                buildNode = buildNodes[n];
                extents = buildNode.extents;

                if (minX > extents[0]) {
                    minX = extents[0];
                }
                if (minY > extents[1]) {
                    minY = extents[1];
                }
                if (minZ > extents[2]) {
                    minZ = extents[2];
                }
                if (maxX < extents[3]) {
                    maxX = extents[3];
                }
                if (maxY < extents[4]) {
                    maxY = extents[4];
                }
                if (maxZ < extents[5]) {
                    maxZ = extents[5];
                }

                /*jshint white: true*/
                lastNodeIndex += 1;
                buildNode.externalNode.spatialIndex = lastNodeIndex;
                this._replaceNode(nodes, lastNodeIndex, buildNode);
            }

            lastNode = nodes[lastNodeIndex];
        } else {
            /* tslint:disable:no-bitwise */
            var splitPosIndex = ((startIndex + endIndex) >> 1);

            if ((startIndex + 1) >= splitPosIndex) {
                buildNode = buildNodes[startIndex];
                buildNode.externalNode.spatialIndex = lastNodeIndex;
                this._replaceNode(nodes, lastNodeIndex, buildNode);
            } else {
                this._recursiveBuild(buildNodes, startIndex, splitPosIndex, lastNodeIndex);
            }

            lastNode = nodes[lastNodeIndex];
            extents = lastNode.extents;
            minX = extents[0];
            minY = extents[1];
            minZ = extents[2];
            maxX = extents[3];
            maxY = extents[4];
            maxZ = extents[5];

            lastNodeIndex = (lastNodeIndex + lastNode.escapeNodeOffset);

            if ((splitPosIndex + 1) >= endIndex) {
                buildNode = buildNodes[splitPosIndex];
                buildNode.externalNode.spatialIndex = lastNodeIndex;
                this._replaceNode(nodes, lastNodeIndex, buildNode);
            } else {
                this._recursiveBuild(buildNodes, splitPosIndex, endIndex, lastNodeIndex);
            }

            lastNode = nodes[lastNodeIndex];
            extents = lastNode.extents;

            if (minX > extents[0]) {
                minX = extents[0];
            }
            if (minY > extents[1]) {
                minY = extents[1];
            }
            if (minZ > extents[2]) {
                minZ = extents[2];
            }
            if (maxX < extents[3]) {
                maxX = extents[3];
            }
            if (maxY < extents[4]) {
                maxY = extents[4];
            }
            if (maxZ < extents[5]) {
                maxZ = extents[5];
            }
            /*jshint white: true*/
        }

        var node = nodes[nodeIndex];
        if (node === undefined) {
            nodes[nodeIndex] = node = AABBTree.allocateNode();
        }
        node.reset(minX, minY, minZ, maxX, maxY, maxZ, (lastNodeIndex + lastNode.escapeNodeOffset - nodeIndex));
    };

    AABBTree.prototype._replaceNode = function (nodes, nodeIndex, newNode) {
        var oldNode = nodes[nodeIndex];
        nodes[nodeIndex] = newNode;
        if (oldNode !== undefined) {
            AABBTree.releaseNode(oldNode);
        }
    };

    AABBTree.prototype._predictNumNodes = function (startIndex, endIndex, lastNodeIndex) {
        lastNodeIndex += 1;

        if ((startIndex + this.numNodesLeaf) >= endIndex) {
            lastNodeIndex += (endIndex - startIndex);
        } else {
            /* tslint:disable:no-bitwise */
            var splitPosIndex = ((startIndex + endIndex) >> 1);

            if ((startIndex + 1) >= splitPosIndex) {
                lastNodeIndex += 1;
            } else {
                lastNodeIndex = this._predictNumNodes(startIndex, splitPosIndex, lastNodeIndex);
            }

            if ((splitPosIndex + 1) >= endIndex) {
                lastNodeIndex += 1;
            } else {
                lastNodeIndex = this._predictNumNodes(splitPosIndex, endIndex, lastNodeIndex);
            }
        }

        return lastNodeIndex;
    };

    AABBTree.prototype.getVisibleNodes = function (planes, visibleNodes, startIndex) {
        var numVisibleNodes = 0;
        if (this.numExternalNodes > 0) {
            var nodes = this.nodes;
            var endNodeIndex = this.endNode;
            var numPlanes = planes.length;
            var storageIndex = (startIndex === undefined) ? visibleNodes.length : startIndex;
            var node, extents, endChildren;
            var n0, n1, n2, p0, p1, p2;
            var isInside, n, plane, d0, d1, d2, distance;
            var nodeIndex = 0;

            for (; ;) {
                node = nodes[nodeIndex];
                extents = node.extents;
                n0 = extents[0];
                n1 = extents[1];
                n2 = extents[2];
                p0 = extents[3];
                p1 = extents[4];
                p2 = extents[5];

                //isInsidePlanesAABB
                isInside = true;
                n = 0;
                do {
                    plane = planes[n];
                    d0 = plane[0];
                    d1 = plane[1];
                    d2 = plane[2];
                    distance = (d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1) + d2 * (d2 < 0 ? n2 : p2));
                    if (distance < plane[3]) {
                        isInside = false;
                        break;
                    }
                    n += 1;
                } while(n < numPlanes);
                if (isInside) {
                    if (node.externalNode) {
                        visibleNodes[storageIndex] = node.externalNode;
                        storageIndex += 1;
                        numVisibleNodes += 1;
                        nodeIndex += 1;
                        if (nodeIndex >= endNodeIndex) {
                            break;
                        }
                    } else {
                        //isFullyInsidePlanesAABB
                        isInside = true;
                        n = 0;
                        do {
                            plane = planes[n];
                            d0 = plane[0];
                            d1 = plane[1];
                            d2 = plane[2];
                            distance = (d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1) + d2 * (d2 > 0 ? n2 : p2));
                            if (distance < plane[3]) {
                                isInside = false;
                                break;
                            }
                            n += 1;
                        } while(n < numPlanes);
                        if (isInside) {
                            endChildren = (nodeIndex + node.escapeNodeOffset);
                            nodeIndex += 1;
                            do {
                                node = nodes[nodeIndex];
                                if (node.externalNode) {
                                    visibleNodes[storageIndex] = node.externalNode;
                                    storageIndex += 1;
                                    numVisibleNodes += 1;
                                }
                                nodeIndex += 1;
                            } while(nodeIndex < endChildren);
                            if (nodeIndex >= endNodeIndex) {
                                break;
                            }
                        } else {
                            nodeIndex += 1;
                        }
                    }
                } else {
                    nodeIndex += node.escapeNodeOffset;
                    if (nodeIndex >= endNodeIndex) {
                        break;
                    }
                }
            }
        }
        return numVisibleNodes;
    };

    AABBTree.prototype.getOverlappingNodes = function (queryExtents, overlappingNodes, startIndex) {
        if (this.numExternalNodes > 0) {
            var queryMinX = queryExtents[0];
            var queryMinY = queryExtents[1];
            var queryMinZ = queryExtents[2];
            var queryMaxX = queryExtents[3];
            var queryMaxY = queryExtents[4];
            var queryMaxZ = queryExtents[5];
            var nodes = this.nodes;
            var endNodeIndex = this.endNode;
            var node, extents, endChildren;
            var numOverlappingNodes = 0;
            var storageIndex = (startIndex === undefined) ? overlappingNodes.length : startIndex;
            var nodeIndex = 0;
            for (; ;) {
                node = nodes[nodeIndex];
                extents = node.extents;
                var minX = extents[0];
                var minY = extents[1];
                var minZ = extents[2];
                var maxX = extents[3];
                var maxY = extents[4];
                var maxZ = extents[5];
                if (queryMinX <= maxX && queryMinY <= maxY && queryMinZ <= maxZ && queryMaxX >= minX && queryMaxY >= minY && queryMaxZ >= minZ) {
                    if (node.externalNode) {
                        overlappingNodes[storageIndex] = node.externalNode;
                        storageIndex += 1;
                        numOverlappingNodes += 1;
                        nodeIndex += 1;
                        if (nodeIndex >= endNodeIndex) {
                            break;
                        }
                    } else {
                        if (queryMaxX >= maxX && queryMaxY >= maxY && queryMaxZ >= maxZ && queryMinX <= minX && queryMinY <= minY && queryMinZ <= minZ) {
                            endChildren = (nodeIndex + node.escapeNodeOffset);
                            nodeIndex += 1;
                            do {
                                node = nodes[nodeIndex];
                                if (node.externalNode) {
                                    overlappingNodes[storageIndex] = node.externalNode;
                                    storageIndex += 1;
                                    numOverlappingNodes += 1;
                                }
                                nodeIndex += 1;
                            } while(nodeIndex < endChildren);
                            if (nodeIndex >= endNodeIndex) {
                                break;
                            }
                        } else {
                            nodeIndex += 1;
                        }
                    }
                } else {
                    nodeIndex += node.escapeNodeOffset;
                    if (nodeIndex >= endNodeIndex) {
                        break;
                    }
                }
            }
            return numOverlappingNodes;
        } else {
            return 0;
        }
    };

    AABBTree.prototype.getSphereOverlappingNodes = function (center, radius, overlappingNodes) {
        if (this.numExternalNodes > 0) {
            var radiusSquared = (radius * radius);
            var centerX = center[0];
            var centerY = center[1];
            var centerZ = center[2];
            var nodes = this.nodes;
            var endNodeIndex = this.endNode;
            var node, extents;
            var numOverlappingNodes = overlappingNodes.length;
            var nodeIndex = 0;
            for (; ;) {
                node = nodes[nodeIndex];
                extents = node.extents;
                var minX = extents[0];
                var minY = extents[1];
                var minZ = extents[2];
                var maxX = extents[3];
                var maxY = extents[4];
                var maxZ = extents[5];
                var totalDistance = 0, sideDistance;
                if (centerX < minX) {
                    sideDistance = (minX - centerX);
                    totalDistance += (sideDistance * sideDistance);
                } else if (centerX > maxX) {
                    sideDistance = (centerX - maxX);
                    totalDistance += (sideDistance * sideDistance);
                }
                if (centerY < minY) {
                    sideDistance = (minY - centerY);
                    totalDistance += (sideDistance * sideDistance);
                } else if (centerY > maxY) {
                    sideDistance = (centerY - maxY);
                    totalDistance += (sideDistance * sideDistance);
                }
                if (centerZ < minZ) {
                    sideDistance = (minZ - centerZ);
                    totalDistance += (sideDistance * sideDistance);
                } else if (centerZ > maxZ) {
                    sideDistance = (centerZ - maxZ);
                    totalDistance += (sideDistance * sideDistance);
                }
                if (totalDistance <= radiusSquared) {
                    nodeIndex += 1;
                    if (node.externalNode) {
                        overlappingNodes[numOverlappingNodes] = node.externalNode;
                        numOverlappingNodes += 1;
                        if (nodeIndex >= endNodeIndex) {
                            break;
                        }
                    }
                } else {
                    nodeIndex += node.escapeNodeOffset;
                    if (nodeIndex >= endNodeIndex) {
                        break;
                    }
                }
            }
        }
    };

    AABBTree.prototype.getOverlappingPairs = function (overlappingPairs, startIndex) {
        if (this.numExternalNodes > 0) {
            var nodes = this.nodes;
            var endNodeIndex = this.endNode;
            var currentNode, currentExternalNode, node, extents;
            var numInsertions = 0;
            var storageIndex = (startIndex === undefined) ? overlappingPairs.length : startIndex;
            var currentNodeIndex = 0, nodeIndex;
            for (; ;) {
                currentNode = nodes[currentNodeIndex];
                while (!currentNode.externalNode) {
                    currentNodeIndex += 1;
                    currentNode = nodes[currentNodeIndex];
                }

                currentNodeIndex += 1;
                if (currentNodeIndex < endNodeIndex) {
                    currentExternalNode = currentNode.externalNode;
                    extents = currentNode.extents;
                    var minX = extents[0];
                    var minY = extents[1];
                    var minZ = extents[2];
                    var maxX = extents[3];
                    var maxY = extents[4];
                    var maxZ = extents[5];

                    nodeIndex = currentNodeIndex;
                    for (; ;) {
                        node = nodes[nodeIndex];
                        extents = node.extents;
                        if (minX <= extents[3] && minY <= extents[4] && minZ <= extents[5] && maxX >= extents[0] && maxY >= extents[1] && maxZ >= extents[2]) {
                            nodeIndex += 1;
                            if (node.externalNode) {
                                overlappingPairs[storageIndex] = currentExternalNode;
                                overlappingPairs[storageIndex + 1] = node.externalNode;
                                storageIndex += 2;
                                numInsertions += 2;
                                if (nodeIndex >= endNodeIndex) {
                                    break;
                                }
                            }
                        } else {
                            nodeIndex += node.escapeNodeOffset;
                            if (nodeIndex >= endNodeIndex) {
                                break;
                            }
                        }
                    }
                } else {
                    break;
                }
            }
            return numInsertions;
        } else {
            return 0;
        }
    };

    AABBTree.prototype.getExtents = function () {
        return (0 < this.nodes.length ? this.nodes[0].extents : null);
    };

    AABBTree.prototype.getRootNode = function () {
        return this.nodes[0];
    };

    AABBTree.prototype.getNodes = function () {
        return this.nodes;
    };

    AABBTree.prototype.getEndNodeIndex = function () {
        return this.endNode;
    };

    AABBTree.prototype.clear = function () {
        if (this.nodes.length) {
            AABBTree.recycleNodes(this.nodes, 0);
        }
        this.endNode = 0;
        this.needsRebuild = false;
        this.needsRebound = false;
        this.numAdds = 0;
        this.numUpdates = 0;
        this.numExternalNodes = 0;
        this.startUpdate = 0x7FFFFFFF;
        this.endUpdate = -0x7FFFFFFF;
        this.ignoreY = false;
    };

    AABBTree.rayTest = function (trees, ray, callback) {
        // convert ray to parametric form
        var origin = ray.origin;
        var direction = ray.direction;

        // values used throughout calculations.
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var d0 = direction[0];
        var d1 = direction[1];
        var d2 = direction[2];
        var id0 = 1 / d0;
        var id1 = 1 / d1;
        var id2 = 1 / d2;

        // evaluate distance factor to a node's extents from ray origin, along direction
        // use this to induce an ordering on which nodes to check.
        function distanceExtents(extents, upperBound) {
            var min0 = extents[0];
            var min1 = extents[1];
            var min2 = extents[2];
            var max0 = extents[3];
            var max1 = extents[4];
            var max2 = extents[5];

            if (min0 <= o0 && o0 <= max0 && min1 <= o1 && o1 <= max1 && min2 <= o2 && o2 <= max2) {
                return 0.0;
            }

            var tmin, tmax;
            var tymin, tymax;
            var del;
            if (d0 >= 0) {
                // Deal with cases where d0 == 0
                del = (min0 - o0);
                tmin = ((del === 0) ? 0 : (del * id0));
                del = (max0 - o0);
                tmax = ((del === 0) ? 0 : (del * id0));
            } else {
                tmin = ((max0 - o0) * id0);
                tmax = ((min0 - o0) * id0);
            }

            if (d1 >= 0) {
                // Deal with cases where d1 == 0
                del = (min1 - o1);
                tymin = ((del === 0) ? 0 : (del * id1));
                del = (max1 - o1);
                tymax = ((del === 0) ? 0 : (del * id1));
            } else {
                tymin = ((max1 - o1) * id1);
                tymax = ((min1 - o1) * id1);
            }

            if ((tmin > tymax) || (tymin > tmax)) {
                return undefined;
            }

            if (tymin > tmin) {
                tmin = tymin;
            }

            if (tymax < tmax) {
                tmax = tymax;
            }

            var tzmin, tzmax;
            if (d2 >= 0) {
                // Deal with cases where d2 == 0
                del = (min2 - o2);
                tzmin = ((del === 0) ? 0 : (del * id2));
                del = (max2 - o2);
                tzmax = ((del === 0) ? 0 : (del * id2));
            } else {
                tzmin = ((max2 - o2) * id2);
                tzmax = ((min2 - o2) * id2);
            }

            if ((tmin > tzmax) || (tzmin > tmax)) {
                return undefined;
            }

            if (tzmin > tmin) {
                tmin = tzmin;
            }

            if (tzmax < tmax) {
                tmax = tzmax;
            }

            if (tmin < 0) {
                tmin = tmax;
            }

            return (0 <= tmin && tmin < upperBound) ? tmin : undefined;
        }

        // we traverse both trees at once
        // keeping a priority list of nodes to check next.
        // TODO: possibly implement priority list more effeciently?
        //       binary heap probably too much overhead in typical case.
        var priorityList = [];

        //current upperBound on distance to first intersection
        //and current closest object properties
        var minimumResult = null;

        //if node is a leaf, intersect ray with shape
        // otherwise insert node into priority list.
        function processNode(tree, nodeIndex, upperBound) {
            var nodes = tree.getNodes();
            var node = nodes[nodeIndex];
            var distance = distanceExtents(node.extents, upperBound);
            if (distance === undefined) {
                return upperBound;
            }

            if (node.externalNode) {
                var result = callback(tree, node.externalNode, ray, distance, upperBound);
                if (result) {
                    minimumResult = result;
                    upperBound = result.factor;
                }
            } else {
                // TODO: change to binary search?
                var length = priorityList.length;
                var i;
                for (i = 0; i < length; i += 1) {
                    var curObj = priorityList[i];
                    if (distance > curObj.distance) {
                        break;
                    }
                }

                //insert node at index i
                priorityList.splice(i - 1, 0, {
                    tree: tree,
                    nodeIndex: nodeIndex,
                    distance: distance
                });
            }

            return upperBound;
        }

        var upperBound = ray.maxFactor;

        var tree;
        var i;
        for (i = 0; i < trees.length; i += 1) {
            tree = trees[i];
            if (tree.endNode !== 0) {
                upperBound = processNode(tree, 0, upperBound);
            }
        }

        while (priorityList.length !== 0) {
            var nodeObj = priorityList.pop();

            if (nodeObj.distance >= upperBound) {
                continue;
            }

            var nodeIndex = nodeObj.nodeIndex;
            tree = nodeObj.tree;
            var nodes = tree.getNodes();

            var node = nodes[nodeIndex];
            var maxIndex = nodeIndex + node.escapeNodeOffset;

            var childIndex = nodeIndex + 1;
            do {
                upperBound = processNode(tree, childIndex, upperBound);
                childIndex += nodes[childIndex].escapeNodeOffset;
            } while(childIndex < maxIndex);
        }

        return minimumResult;
    };

    AABBTree.create = function (highQuality) {
        return new AABBTree(highQuality ? true : false);
    };
    AABBTree.version = 1;

    AABBTree.useFloat32Array = false;

    AABBTree.nodesPoolAllocationSize = 128;
    AABBTree.nodesPool = [];
    return AABBTree;
})();
;

//
// Detect correct typed arrays
((function () {
    if (typeof Float32Array !== "undefined") {
        var testArray = new Float32Array(4);
        var textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            AABBTree.useFloat32Array = true;
        }
    }
})());

// Copyright (c) 2010-2011 Turbulenz Limited
//
// Observer
//
var Observer = (function () {
    function Observer() {
    }
    Observer.prototype.subscribe = function (subscriber) {
        //Check for duplicates
        var subscribers = this.subscribers;
        var length = subscribers.length;
        for (var index = 0; index < length; index += 1) {
            if (subscribers[index] === subscriber) {
                return;
            }
        }

        subscribers.push(subscriber);
    };

    Observer.prototype.unsubscribe = function (subscriber) {
        var subscribers = this.subscribers;
        var length = subscribers.length;
        for (var index = 0; index < length; index += 1) {
            if (subscribers[index] === subscriber) {
                subscribers.splice(index, 1);
                break;
            }
        }
    };

    Observer.prototype.unsubscribeAll = function (/* subscriber */ ) {
        this.subscribers.length = 0;
    };

    // this function can take any number of arguments
    // they are passed on to the subscribers
    // NOTE: if we write (... args: any[]), TSC inserts code to copy
    // the args into an array.
    Observer.prototype.notify = function (a0, a1, a2, a3, a4, a5, a6, a7, a8, a9, a10, a11) {
        // Note that the callbacks might unsubscribe
        var subscribers = this.subscribers;
        var length = this.subscribers.length;
        var index = 0;

        while (index < length) {
            subscribers[index].apply(null, arguments);
            if (subscribers.length === length) {
                index += 1;
            } else {
                length = subscribers.length;
            }
        }
    };

    Observer.create = function () {
        var observer = new Observer();
        observer.subscribers = [];
        return observer;
    };
    return Observer;
})();

// Copyright (c) 2011-2013 Turbulenz Limited
/*global TurbulenzEngine*/
/*global Uint8Array*/
/*global Uint16Array*/
/*global window*/

//
// DDSLoader
//
var DDSLoader = (function () {
    function DDSLoader() {
    }
    DDSLoader.prototype.processBytes = function (bytes, status) {
        if (!this.isValidHeader(bytes)) {
            this.onerror(status);
            return;
        }

        // Skip signature
        var offset = 4;

        var header = this.parseHeader(bytes, offset);
        offset += 31 * 4;

        this.width = header.dwWidth;
        this.height = header.dwHeight;

        if ((header.dwCaps2 & this.DDSF_VOLUME) && (header.dwDepth > 0)) {
            this.depth = header.dwDepth;
        } else {
            this.depth = 1;
        }

        if (header.dwFlags & this.DDSF_MIPMAPCOUNT) {
            this.numLevels = header.dwMipMapCount;
        } else {
            this.numLevels = 1;
        }

        if (header.dwCaps2 & this.DDSF_CUBEMAP) {
            var numFaces = 0;
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_POSITIVEX) ? 1 : 0);
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_NEGATIVEX) ? 1 : 0);
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_POSITIVEY) ? 1 : 0);
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_NEGATIVEY) ? 1 : 0);
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_POSITIVEZ) ? 1 : 0);
            numFaces += ((header.dwCaps2 & this.DDSF_CUBEMAP_NEGATIVEZ) ? 1 : 0);

            if (numFaces !== 6 || this.width !== this.height) {
                this.onerror(status);
                return;
            }

            this.numFaces = numFaces;
        } else {
            this.numFaces = 1;
        }

        var compressed = false;
        var bpe = 0;

        // figure out what the image format is
        var gd = this.gd;
        if (header.ddspf.dwFlags & this.DDSF_FOURCC) {
            switch (header.ddspf.dwFourCC) {
                case this.FOURCC_DXT1:
                    this.format = gd.PIXELFORMAT_DXT1;
                    bpe = 8;
                    compressed = true;
                    break;

                case this.FOURCC_DXT2:
                case this.FOURCC_DXT3:
                    this.format = gd.PIXELFORMAT_DXT3;
                    bpe = 16;
                    compressed = true;
                    break;

                case this.FOURCC_DXT4:
                case this.FOURCC_DXT5:
                case this.FOURCC_RXGB:
                    this.format = gd.PIXELFORMAT_DXT5;
                    bpe = 16;
                    compressed = true;
                    break;

                case this.FOURCC_R8G8B8:
                    this.bgrFormat = this.BGRPIXELFORMAT_B8G8R8;
                    bpe = 3;
                    break;

                case this.FOURCC_A8R8G8B8:
                    this.bgrFormat = this.BGRPIXELFORMAT_B8G8R8A8;
                    bpe = 4;
                    break;

                case this.FOURCC_R5G6B5:
                    this.bgrFormat = this.BGRPIXELFORMAT_B5G6R5;
                    bpe = 2;
                    break;

                case this.FOURCC_A8:
                    this.format = gd.PIXELFORMAT_A8;
                    bpe = 1;
                    break;

                case this.FOURCC_A8B8G8R8:
                    this.format = gd.PIXELFORMAT_R8G8B8A8;
                    bpe = 4;
                    break;

                case this.FOURCC_L8:
                    this.format = gd.PIXELFORMAT_L8;
                    bpe = 1;
                    break;

                case this.FOURCC_A8L8:
                    this.format = gd.PIXELFORMAT_L8A8;
                    bpe = 2;
                    break;

                case this.FOURCC_UNKNOWN:
                case this.FOURCC_ATI1:
                case this.FOURCC_ATI2:
                case this.FOURCC_X8R8G8B8:
                case this.FOURCC_X8B8G8R8:
                case this.FOURCC_A2B10G10R10:
                case this.FOURCC_A2R10G10B10:
                case this.FOURCC_A16B16G16R16:
                case this.FOURCC_R16F:
                case this.FOURCC_A16B16G16R16F:
                case this.FOURCC_R32F:
                case this.FOURCC_A32B32G32R32F:
                case this.FOURCC_L16:
                case this.FOURCC_X1R5G5B5:
                case this.FOURCC_A1R5G5B5:
                case this.FOURCC_A4R4G4B4:
                case this.FOURCC_R3G3B2:
                case this.FOURCC_A8R3G3B2:
                case this.FOURCC_X4R4G4B4:
                case this.FOURCC_A4L4:
                case this.FOURCC_D16_LOCKABLE:
                case this.FOURCC_D32:
                case this.FOURCC_D24X8:
                case this.FOURCC_D16:
                case this.FOURCC_D32F_LOCKABLE:
                case this.FOURCC_G16R16:
                case this.FOURCC_G16R16F:
                case this.FOURCC_G32R32F:
                    break;

                default:
                    this.onerror(status);
                    return;
            }
        } else if (header.ddspf.dwFlags === this.DDSF_RGBA && header.ddspf.dwRGBBitCount === 32) {
            if (header.ddspf.dwRBitMask === 0x000000FF && header.ddspf.dwGBitMask === 0x0000FF00 && header.ddspf.dwBBitMask === 0x00FF0000 && header.ddspf.dwABitMask === 0xFF000000) {
                this.format = gd.PIXELFORMAT_R8G8B8A8;
            } else {
                this.bgrFormat = this.BGRPIXELFORMAT_B8G8R8A8;
            }
            bpe = 4;
        } else if (header.ddspf.dwFlags === this.DDSF_RGB && header.ddspf.dwRGBBitCount === 32) {
            if (header.ddspf.dwRBitMask === 0x000000FF && header.ddspf.dwGBitMask === 0x0000FF00 && header.ddspf.dwBBitMask === 0x00FF0000) {
                this.format = gd.PIXELFORMAT_R8G8B8A8;
            } else {
                this.bgrFormat = this.BGRPIXELFORMAT_B8G8R8A8;
            }
            bpe = 4;
        } else if (header.ddspf.dwFlags === this.DDSF_RGB && header.ddspf.dwRGBBitCount === 24) {
            if (header.ddspf.dwRBitMask === 0x000000FF && header.ddspf.dwGBitMask === 0x0000FF00 && header.ddspf.dwBBitMask === 0x00FF0000) {
                this.format = gd.PIXELFORMAT_R8G8B8;
            } else {
                this.bgrFormat = this.BGRPIXELFORMAT_B8G8R8;
            }
            bpe = 3;
        } else if (header.ddspf.dwFlags === this.DDSF_RGB && header.ddspf.dwRGBBitCount === 16) {
            if (header.ddspf.dwRBitMask === 0x0000F800 && header.ddspf.dwGBitMask === 0x000007E0 && header.ddspf.dwBBitMask === 0x0000001F) {
                this.format = gd.PIXELFORMAT_R5G6B5;
            } else {
                this.bgrFormat = this.BGRPIXELFORMAT_B5G6R5;
            }
            bpe = 2;
        } else if (header.ddspf.dwRGBBitCount === 8) {
            this.format = gd.PIXELFORMAT_L8;
            bpe = 1;
        } else {
            this.onerror(status);
            return;
        }

        var size = 0;
        for (var face = 0; face < this.numFaces; face += 1) {
            var w = this.width, h = this.height, d = this.depth;
            for (var level = 0; level < this.numLevels; level += 1) {
                var ew = (compressed ? Math.floor((w + 3) / 4) : w);
                var eh = (compressed ? Math.floor((h + 3) / 4) : h);
                size += (ew * eh * d * bpe);

                w = (w > 1 ? (w >> 1) : 1);
                h = (h > 1 ? (h >> 1) : 1);
                d = (d > 1 ? (d >> 1) : 1);
            }
        }

        if (bytes.length < (offset + size)) {
            this.onerror(status);
            return;
        }

        this.bytesPerPixel = bpe;

        var data = bytes.subarray(offset);
        bytes = null;

        var swapBytes = false;
        switch (this.bgrFormat) {
            case this.BGRPIXELFORMAT_B8G8R8:
                this.format = gd.PIXELFORMAT_R8G8B8;
                swapBytes = true;
                break;
            case this.BGRPIXELFORMAT_B8G8R8A8:
                this.format = gd.PIXELFORMAT_R8G8B8A8;
                swapBytes = true;
                break;
            case this.BGRPIXELFORMAT_B5G6R5:
                this.format = gd.PIXELFORMAT_R5G6B5;
                swapBytes = true;
                break;
            default:
                break;
        }

        if (swapBytes) {
            data = this.convertBGR2RGB(data);
        }

        if (this.format === gd.PIXELFORMAT_DXT1) {
            if (!gd.isSupported('TEXTURE_DXT1')) {
                if (this.hasDXT1Alpha(data)) {
                    this.format = gd.PIXELFORMAT_R5G5B5A1;
                    DDSLoader.decodeInWorker(DDSLoader.WorkerCommand.DXT1A, data, this);
                    return;
                } else {
                    this.format = gd.PIXELFORMAT_R5G6B5;
                    DDSLoader.decodeInWorker(DDSLoader.WorkerCommand.DXT1, data, this);
                    return;
                }
            }
        } else if (this.format === gd.PIXELFORMAT_DXT3) {
            if (!gd.isSupported('TEXTURE_DXT3')) {
                this.format = gd.PIXELFORMAT_R4G4B4A4;
                DDSLoader.decodeInWorker(DDSLoader.WorkerCommand.DXT3, data, this);
                return;
            }
        } else if (this.format === gd.PIXELFORMAT_DXT5) {
            if (!gd.isSupported('TEXTURE_DXT5')) {
                this.format = gd.PIXELFORMAT_R4G4B4A4;
                DDSLoader.decodeInWorker(DDSLoader.WorkerCommand.DXT5, data, this);
                return;
            }
        }

        this.onload(data, this.width, this.height, this.format, this.numLevels, (this.numFaces > 1), this.depth, status);
    };

    DDSLoader.createWorker = function (index) {
        var code = "var convertDXT1To565 = " + this.convertDXT1To565.toString() + ";\n" + "var convertDXT1To5551 = " + this.convertDXT1To5551.toString() + ";\n" + "var convertDXT3To4444 = " + this.convertDXT3To4444.toString() + ";\n" + "var convertDXT5To4444 = " + this.convertDXT5To4444.toString() + ";\n" + "var command, srcWidth, srcHeight, srcNumLevels, srcNumFaces, srcOffset;\n" + "onmessage = function decoderOnMessage(event)\n" + "{\n" + "    var edata = event.data;\n" + "    if (edata instanceof ArrayBuffer)\n" + "    {\n" + "        var data = new Uint8Array(edata, srcOffset);\n" + "        switch (command)\n" + "        {\n" + "            case 0: // DXT1\n" + "                data = convertDXT1To565(data, srcWidth, srcHeight, srcNumLevels, srcNumFaces);\n" + "                break;\n" + "            case 1: // DXT1A\n" + "                data = convertDXT1To5551(data, srcWidth, srcHeight, srcNumLevels, srcNumFaces);\n" + "                break;\n" + "            case 2: // DXT3\n" + "                data = convertDXT3To4444(data, srcWidth, srcHeight, srcNumLevels, srcNumFaces);\n" + "                break;\n" + "            case 3: // DXT4\n" + "                data = convertDXT5To4444(data, srcWidth, srcHeight, srcNumLevels, srcNumFaces);\n" + "                break;\n" + "            default:\n" + "                data = null;\n" + "                break;\n" + "        };\n" + "        if (data)\n" + "        {\n" + "            postMessage(data.buffer, [data.buffer]);\n" + "        }\n" + "        else\n" + "        {\n" + "            postMessage(null);\n" + "        }\n" + "    }\n" + "    else\n" + "    {\n" + "        command = edata.command;\n" + "        srcWidth = edata.width;\n" + "        srcHeight = edata.height;\n" + "        srcNumLevels = edata.numLevels;\n" + "        srcNumFaces = edata.numFaces;\n" + "        srcOffset = edata.byteOffset;\n" + "    }\n" + "};";
        var blob = new Blob([code], { type: "text/javascript" });

        var url = (typeof URL !== "undefined" ? URL : window['webkitURL']);
        var objectURL = url.createObjectURL(blob);

        var worker;
        try  {
            worker = new Worker(objectURL);
        } catch (e) {
            worker = null;
        }
        if (worker) {
            var workerQueue = DDSLoader.workerQueues[index];
            worker.onmessage = function (event) {
                var loader = workerQueue.shift();

                worker['load'] -= ((((loader.width + 3) * (loader.height + 3)) >> 4) * loader.numLevels * loader.numFaces);

                var data = event.data;
                if (data) {
                    loader.onload(data, loader.width, loader.height, loader.format, loader.numLevels, (loader.numFaces > 1), loader.depth, 200);
                } else {
                    loader.onerror(200);
                }
            };
            worker['load'] = 0;
        }

        url.revokeObjectURL(objectURL);

        return worker;
    };

    DDSLoader.decodeInWorker = function (command, data, loader) {
        var maxNumWorkers = this.maxNumWorkers;
        if (maxNumWorkers) {
            var workerQueues = this.workerQueues;
            var workers = this.workers;
            var workerIndex = -1;
            var n;
            if (!workers) {
                this.workerQueues = workerQueues = [];
                this.workers = workers = [];
                workerIndex = 0;
                for (n = 0; n < maxNumWorkers; n += 1) {
                    workerQueues[n] = [];
                    workers[n] = this.createWorker(n);
                    if (!workers[n]) {
                        workerIndex = -1;
                        break;
                    }
                }
            } else {
                workerIndex = 0;
                var minLoad = workers[0]['load'];
                for (n = 1; n < maxNumWorkers; n += 1) {
                    var load = workers[n]['load'];
                    if (minLoad > load) {
                        minLoad = load;
                        workerIndex = n;
                    }
                }
            }

            if (workerIndex !== -1) {
                var worker = workers[workerIndex];

                worker['load'] += ((((loader.width + 3) * (loader.height + 3)) >> 4) * loader.numLevels * loader.numFaces);

                //console.log(workerQueues[0].length, workerQueues[1].length, workerQueues[2].length, workerQueues[3].length);
                workerQueues[workerIndex].push(loader);

                var byteOffset = data.byteOffset;
                var buffer;
                if (loader.externalBuffer) {
                    buffer = data.buffer.slice(byteOffset, (byteOffset + data.byteLength));
                    byteOffset = 0;
                } else {
                    buffer = data.buffer;
                }

                // First post the command (structural copy)
                worker.postMessage({
                    command: command,
                    width: loader.width,
                    height: loader.height,
                    numLevels: loader.numLevels,
                    numFaces: loader.numFaces,
                    byteOffset: byteOffset
                });

                // Then post the data (ownership transfer)
                worker.postMessage(buffer, [buffer]);

                return;
            } else {
                this.maxNumWorkers = 0;
            }
        }

        // Workers not available, use timeout
        var decoder;
        switch (command) {
            case DDSLoader.WorkerCommand.DXT1:
                decoder = function () {
                    data = DDSLoader.convertDXT1To565(data, loader.width, loader.height, loader.numLevels, loader.numFaces);
                    loader.onload(data, loader.width, loader.height, loader.format, loader.numLevels, (loader.numFaces > 1), loader.depth, 200);
                };
                break;
            case DDSLoader.WorkerCommand.DXT1A:
                decoder = function () {
                    data = DDSLoader.convertDXT1To5551(data, loader.width, loader.height, loader.numLevels, loader.numFaces);
                    loader.onload(data, loader.width, loader.height, loader.format, loader.numLevels, (loader.numFaces > 1), loader.depth, 200);
                };
                break;
            case DDSLoader.WorkerCommand.DXT3:
                decoder = function () {
                    data = DDSLoader.convertDXT3To4444(data, loader.width, loader.height, loader.numLevels, loader.numFaces);
                    loader.onload(data, loader.width, loader.height, loader.format, loader.numLevels, (loader.numFaces > 1), loader.depth, 200);
                };
                break;
            case DDSLoader.WorkerCommand.DXT5:
                decoder = function () {
                    data = DDSLoader.convertDXT5To4444(data, loader.width, loader.height, loader.numLevels, loader.numFaces);
                    loader.onload(data, loader.width, loader.height, loader.format, loader.numLevels, (loader.numFaces > 1), loader.depth, 200);
                };
                break;
            default:
                decoder = null;
                break;
        }
        ;
        if (decoder) {
            TurbulenzEngine.setTimeout(decoder, 0);
        } else {
            loader.onerror(200);
        }
    };

    DDSLoader.prototype.parseHeader = function (bytes, offset) {
        function readUInt32() {
            var value = ((bytes[offset]) | (bytes[offset + 1] << 8) | (bytes[offset + 2] << 16) | (bytes[offset + 3] << 24));
            offset += 4;
            return value;
        }

        function parsePixelFormatHeader() {
            return {
                dwSize: readUInt32(),
                dwFlags: readUInt32(),
                dwFourCC: readUInt32(),
                dwRGBBitCount: readUInt32(),
                dwRBitMask: readUInt32(),
                dwGBitMask: readUInt32(),
                dwBBitMask: readUInt32(),
                dwABitMask: readUInt32()
            };
        }

        var header = {
            dwSize: readUInt32(),
            dwFlags: readUInt32(),
            dwHeight: readUInt32(),
            dwWidth: readUInt32(),
            dwPitchOrLinearSize: readUInt32(),
            dwDepth: readUInt32(),
            dwMipMapCount: readUInt32(),
            dwReserved1: [
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32(),
                readUInt32()
            ],
            ddspf: parsePixelFormatHeader(),
            dwCaps1: readUInt32(),
            dwCaps2: readUInt32(),
            dwReserved2: [readUInt32(), readUInt32(), readUInt32()]
        };

        return header;
    };

    DDSLoader.prototype.isValidHeader = function (bytes) {
        return (68 === bytes[0] && 68 === bytes[1] && 83 === bytes[2] && 32 === bytes[3]);
    };

    DDSLoader.prototype.convertBGR2RGB = function (data) {
        // Rearrange the colors from BGR to RGB
        var bytesPerPixel = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var numLevels = this.numLevels;
        var numFaces = this.numFaces;

        var numPixels = 0;
        for (var level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? Math.floor(width / 2) : 1);
            height = (height > 1 ? Math.floor(height / 2) : 1);
        }

        var size = (numPixels * bytesPerPixel * numFaces);
        var offset = 0;
        if (bytesPerPixel === 3 || bytesPerPixel === 4) {
            do {
                var tmp = data[offset];
                data[offset] = data[offset + 2];
                data[offset + 2] = tmp;
                offset += bytesPerPixel;
            } while(offset < size);
        } else if (bytesPerPixel === 2) {
            var dst = new Uint16Array(numPixels * numFaces);
            var src = 0, dest = 0;
            var r, g, b;

            /*jshint bitwise: false*/
            var mask5bit = ((1 << 5) - 1);
            var midMask6bit = (((1 << 6) - 1) << 5);
            do {
                var value = ((data[src + 1] << 8) | data[src]);
                src += 2;
                r = (value & mask5bit) << 11;
                g = (value & midMask6bit);
                b = ((value >> 11) & mask5bit);
                dst[dest] = r | g | b;
                dest += 1;
            } while(offset < size);

            /*jshint bitwise: true*/
            return dst;
        }
        return data;
    };

    DDSLoader.prototype.decode565 = function (value, color) {
        /*jshint bitwise: false*/
        var r = ((value >> 11) & 31);
        var g = ((value >> 5) & 63);
        var b = ((value) & 31);
        color[0] = ((r << 3) | (r >> 2));
        color[1] = ((g << 2) | (g >> 4));
        color[2] = ((b << 3) | (b >> 2));
        color[3] = 255;

        /*jshint bitwise: true*/
        return color;
    };

    DDSLoader.prototype.decodeColor = function (data, src, isDXT1, out, scratchpad) {
        /*jshint bitwise: false*/
        var cache = scratchpad.cache;
        var decode565 = DDSLoader.prototype.decode565;
        var col0 = ((data[src + 1] << 8) | data[src]);
        src += 2;
        var col1 = ((data[src + 1] << 8) | data[src]);
        src += 2;

        var c0, c1, c2, c3, i;
        if (col0 !== col1) {
            c0 = decode565(col0, cache[0]);
            c1 = decode565(col1, cache[1]);
            c2 = cache[2];
            c3 = cache[3];

            if (col0 > col1) {
                for (i = 0; i < 3; i += 1) {
                    var c0i = c0[i];
                    var c1i = c1[i];
                    c2[i] = ((((c0i * 2) + c1i) / 3) | 0);
                    c3[i] = (((c0i + (c1i * 2)) / 3) | 0);
                }
                c2[3] = 255;
                c3[3] = 255;
            } else {
                for (i = 0; i < 3; i += 1) {
                    c2[i] = ((c0[i] + c1[i]) >> 1);
                    c3[i] = 0;
                }
                c2[3] = 255;
                c3[3] = 0;
            }
        } else {
            c0 = decode565(col0, cache[0]);
            c1 = c0;
            c2 = c0;
            c3 = cache[1];
            for (i = 0; i < 4; i += 1) {
                c3[i] = 0;
            }
        }

        var c = scratchpad.colorArray;
        c[0] = c0;
        c[1] = c1;
        c[2] = c2;
        c[3] = c3;

        // ((1 << 2) - 1) === 3;
        var row, dest, color;
        if (isDXT1) {
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];
                dest[0] = c[(row) & 3];
                dest[1] = c[(row >> 2) & 3];
                dest[2] = c[(row >> 4) & 3];
                dest[3] = c[(row >> 6) & 3];
            }
        } else {
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];

                color = c[(row) & 3];
                dest[0][0] = color[0];
                dest[0][1] = color[1];
                dest[0][2] = color[2];
                dest[0][3] = color[3];

                color = c[(row >> 2) & 3];
                dest[1][0] = color[0];
                dest[1][1] = color[1];
                dest[1][2] = color[2];
                dest[1][3] = color[3];

                color = c[(row >> 4) & 3];
                dest[2][0] = color[0];
                dest[2][1] = color[1];
                dest[2][2] = color[2];
                dest[2][3] = color[3];

                color = c[(row >> 6) & 3];
                dest[3][0] = color[0];
                dest[3][1] = color[1];
                dest[3][2] = color[2];
                dest[3][3] = color[3];
            }
        }
        /*jshint bitwise: true*/
    };

    DDSLoader.prototype.decodeDXT3Alpha = function (data, src, out) {
        for (var i = 0; i < 4; i += 1) {
            var row = ((data[src + 1] << 8) | data[src]);
            src += 2;
            var dest = out[i];
            if (row) {
                dest[0][3] = ((row) & 15) * (255 / 15);
                dest[1][3] = ((row >> 4) & 15) * (255 / 15);
                dest[2][3] = ((row >> 8) & 15) * (255 / 15);
                dest[3][3] = ((row >> 12) & 15) * (255 / 15);
            } else {
                dest[0][3] = 0;
                dest[1][3] = 0;
                dest[2][3] = 0;
                dest[3][3] = 0;
            }
        }
        /*jshint bitwise: true*/
    };

    DDSLoader.prototype.decodeDXT5Alpha = function (data, src, out, scratchpad) {
        var a0 = data[src];
        src += 1;
        var a1 = data[src];
        src += 1;

        /*jshint bitwise: false*/
        var a = scratchpad.alphaArray;

        a[0] = a0;
        a[1] = a1;
        if (a0 > a1) {
            a[2] = ((((a0 * 6) + (a1 * 1)) / 7) | 0);
            a[3] = ((((a0 * 5) + (a1 * 2)) / 7) | 0);
            a[4] = ((((a0 * 4) + (a1 * 3)) / 7) | 0);
            a[5] = ((((a0 * 3) + (a1 * 4)) / 7) | 0);
            a[6] = ((((a0 * 2) + (a1 * 5)) / 7) | 0);
            a[7] = ((((a0 * 1) + (a1 * 6)) / 7) | 0);
        } else if (a0 < a1) {
            a[2] = ((((a0 * 4) + (a1 * 1)) / 5) | 0);
            a[3] = ((((a0 * 3) + (a1 * 2)) / 5) | 0);
            a[4] = ((((a0 * 2) + (a1 * 3)) / 5) | 0);
            a[5] = ((((a0 * 1) + (a1 * 4)) / 5) | 0);
            a[6] = 0;
            a[7] = 255;
        } else {
            a[2] = a0;
            a[3] = a0;
            a[4] = a0;
            a[5] = a0;
            a[6] = 0;
            a[7] = 255;
        }

        // ((1 << 3) - 1) === 7
        var dest;
        for (var i = 0; i < 2; i += 1) {
            var value = (data[src] | (data[src + 1] << 8) | (data[src + 2] << 16));
            src += 3;
            dest = out[(i * 2)];
            dest[0][3] = a[(value) & 7];
            dest[1][3] = a[(value >> 3) & 7];
            dest[2][3] = a[(value >> 6) & 7];
            dest[3][3] = a[(value >> 9) & 7];
            dest = out[(i * 2) + 1];
            dest[0][3] = a[(value >> 12) & 7];
            dest[1][3] = a[(value >> 15) & 7];
            dest[2][3] = a[(value >> 18) & 7];
            dest[3][3] = a[(value >> 21) & 7];
        }
        /*jshint bitwise: true*/
    };

    DDSLoader.prototype.convertToRGBA32 = function (data, decode, srcStride) {
        //var bpp = 4;
        var level;
        var width = this.width;
        var height = this.height;
        var numLevels = this.numLevels;
        var numFaces = this.numFaces;

        /*jshint bitwise: false*/
        var numPixels = 0;
        for (level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? (width >> 1) : 1);
            height = (height > 1 ? (height >> 1) : 1);
        }

        var dst = new Uint8Array(numPixels * 4 * numFaces);

        var src = 0, dest = 0;

        var color = [
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)]
        ];
        for (var face = 0; face < numFaces; face += 1) {
            width = this.width;
            height = this.height;
            for (var n = 0; n < numLevels; n += 1) {
                var numColumns = (width > 4 ? 4 : width);
                var numLines = (height > 4 ? 4 : height);
                var heightInBlocks = ((height + 3) >> 2);
                var widthInBlocks = ((width + 3) >> 2);
                var desinationStride = (width * 4);
                var desinationLineStride = (numColumns * 4);
                var desinationBlockStride = (desinationStride * (numLines - 1));
                for (var y = 0; y < heightInBlocks; y += 1) {
                    for (var x = 0; x < widthInBlocks; x += 1) {
                        decode(data, src, color);
                        var destLine = dest;
                        for (var line = 0; line < numLines; line += 1) {
                            var colorLine = color[line];
                            var destRGBA = destLine;
                            for (var i = 0; i < numColumns; i += 1) {
                                var rgba = colorLine[i];
                                dst[destRGBA] = rgba[0];
                                dst[destRGBA + 1] = rgba[1];
                                dst[destRGBA + 2] = rgba[2];
                                dst[destRGBA + 3] = rgba[3];
                                destRGBA += 4;
                            }
                            destLine += desinationStride;
                        }
                        src += srcStride;
                        dest += desinationLineStride;
                    }
                    dest += desinationBlockStride;
                }

                width = (width > 1 ? (width >> 1) : 1);
                height = (height > 1 ? (height >> 1) : 1);
            }
        }

        /*jshint bitwise: true*/
        return dst;
    };

    DDSLoader.prototype.hasDXT1Alpha = function (data) {
        var length16 = (data.length >>> 1);
        var data16 = new Uint16Array(data.buffer, data.byteOffset, length16);
        var n, b, i, row;
        for (n = 0; n < length16; n += 4) {
            if (data16[n] <= data16[n + 1]) {
                b = ((n + 2) << 1);
                for (i = 0; i < 4; i += 1) {
                    row = data[b + i];
                    if (2 < row) {
                        if (((row) & 3) === 3 || ((row >> 2) & 3) === 3 || ((row >> 4) & 3) === 3 || ((row >> 6) & 3) === 3) {
                            return true;
                        }
                    }
                }
            }
        }
        return false;
    };

    DDSLoader.prototype.encodeR5G6B5 = function (rgba) {
        return (((rgba[2] & 0xf8) >>> 3) | ((rgba[1] & 0xfc) << 3) | ((rgba[0] & 0xf8) << 8));
    };

    DDSLoader.prototype.encodeR5G5B5A1 = function (rgba) {
        return ((rgba[3] >>> 7) | ((rgba[2] & 0xf8) >>> 2) | ((rgba[1] & 0xf8) << 3) | ((rgba[0] & 0xf8) << 8));
    };

    DDSLoader.prototype.encodeR4G4B4A4 = function (rgba) {
        return ((rgba[3] >>> 4) | (rgba[2] & 0xf0) | ((rgba[1] & 0xf0) << 4) | ((rgba[0] & 0xf0) << 8));
    };

    DDSLoader.convertDXT1To565 = function (srcBuffer, srcWidth, srcHeight, srcNumLevels, srcNumFaces) {
        function decodeDXT1Color(data, src, out, cache, colorArray) {
            function decode565(value, color) {
                /*jshint bitwise: false*/
                var r = ((value >> 11) & 31);
                var g = ((value >> 5) & 63);
                var b = ((value) & 31);
                color[0] = ((r << 3) | (r >> 2));
                color[1] = ((g << 2) | (g >> 4));
                color[2] = ((b << 3) | (b >> 2));

                /*jshint bitwise: true*/
                return color;
            }

            /*jshint bitwise: false*/
            var col0 = ((data[src + 1] << 8) | data[src]);
            src += 2;
            var col1 = ((data[src + 1] << 8) | data[src]);
            src += 2;

            var c0, c1, c2, c3, i;
            if (col0 !== col1) {
                c0 = decode565(col0, cache[0]);
                c1 = decode565(col1, cache[1]);
                c2 = cache[2];
                c3 = cache[3];

                if (col0 > col1) {
                    for (i = 0; i < 3; i += 1) {
                        var c0i = c0[i];
                        var c1i = c1[i];
                        c2[i] = ((((c0i * 2) + c1i) / 3) | 0);
                        c3[i] = (((c0i + (c1i * 2)) / 3) | 0);
                    }
                } else {
                    for (i = 0; i < 3; i += 1) {
                        c2[i] = ((c0[i] + c1[i]) >>> 1);
                        c3[i] = 0;
                    }
                }
            } else {
                c0 = decode565(col0, cache[0]);
                c1 = c0;
                c2 = c0;
                c3 = cache[1];
            }

            var c = colorArray;
            c[0] = (((c0[2] & 0xf8) >>> 3) | ((c0[1] & 0xfc) << 3) | ((c0[0] & 0xf8) << 8));
            c[1] = (((c1[2] & 0xf8) >>> 3) | ((c1[1] & 0xfc) << 3) | ((c1[0] & 0xf8) << 8));
            c[2] = (((c2[2] & 0xf8) >>> 3) | ((c2[1] & 0xfc) << 3) | ((c2[0] & 0xf8) << 8));
            c[3] = (((c3[2] & 0xf8) >>> 3) | ((c3[1] & 0xfc) << 3) | ((c3[0] & 0xf8) << 8));

            // ((1 << 2) - 1) === 3;
            var row, dest, color;
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];
                dest[0] = c[(row) & 3];
                dest[1] = c[(row >> 2) & 3];
                dest[2] = c[(row >> 4) & 3];
                dest[3] = c[(row >> 6) & 3];
            }
            /*jshint bitwise: true*/
        }

        //var bpp = 2;
        var level;
        var width = srcWidth;
        var height = srcHeight;
        var numLevels = srcNumLevels;
        var numFaces = srcNumFaces;

        /*jshint bitwise: false*/
        var numPixels = 0;
        for (level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? (width >> 1) : 1);
            height = (height > 1 ? (height >> 1) : 1);
        }

        var dst = new Uint16Array(numPixels * 1 * numFaces);

        var src = 0, dest = 0;

        var color = [new Uint16Array(4), new Uint16Array(4), new Uint16Array(4), new Uint16Array(4)];
        var cache = [new Uint8Array(3), new Uint8Array(3), new Uint8Array(3), new Uint8Array(3)];
        var colorArray = new Uint16Array(4);

        for (var face = 0; face < numFaces; face += 1) {
            width = srcWidth;
            height = srcHeight;
            for (var n = 0; n < numLevels; n += 1) {
                var numColumns = (width > 4 ? 4 : width);
                var numLines = (height > 4 ? 4 : height);
                var heightInBlocks = ((height + 3) >> 2);
                var widthInBlocks = ((width + 3) >> 2);
                var desinationStride = (width * 1);
                var desinationLineStride = (numColumns * 1);
                var desinationBlockStride = (desinationStride * (numLines - 1));
                for (var y = 0; y < heightInBlocks; y += 1) {
                    for (var x = 0; x < widthInBlocks; x += 1) {
                        decodeDXT1Color(srcBuffer, src, color, cache, colorArray);
                        var destLine = dest;
                        for (var line = 0; line < numLines; line += 1) {
                            var colorLine = color[line];
                            var destRGBA = destLine;
                            for (var i = 0; i < numColumns; i += 1) {
                                dst[destRGBA] = colorLine[i];
                                destRGBA += 1;
                            }
                            destLine += desinationStride;
                        }
                        src += 8;
                        dest += desinationLineStride;
                    }
                    dest += desinationBlockStride;
                }

                width = (width > 1 ? (width >> 1) : 1);
                height = (height > 1 ? (height >> 1) : 1);
            }
        }

        /*jshint bitwise: true*/
        return dst;
    };

    DDSLoader.convertDXT1To5551 = function (srcBuffer, srcWidth, srcHeight, srcNumLevels, srcNumFaces) {
        function decodeDXT1Color(data, src, out, cache, colorArray) {
            function decode565(value, color) {
                /*jshint bitwise: false*/
                var r = ((value >> 11) & 31);
                var g = ((value >> 5) & 63);
                var b = ((value) & 31);
                color[0] = ((r << 3) | (r >> 2));
                color[1] = ((g << 2) | (g >> 4));
                color[2] = ((b << 3) | (b >> 2));
                color[3] = 255;

                /*jshint bitwise: true*/
                return color;
            }

            /*jshint bitwise: false*/
            var col0 = ((data[src + 1] << 8) | data[src]);
            src += 2;
            var col1 = ((data[src + 1] << 8) | data[src]);
            src += 2;

            var c0, c1, c2, c3, i;
            if (col0 !== col1) {
                c0 = decode565(col0, cache[0]);
                c1 = decode565(col1, cache[1]);
                c2 = cache[2];
                c3 = cache[3];

                if (col0 > col1) {
                    for (i = 0; i < 3; i += 1) {
                        var c0i = c0[i];
                        var c1i = c1[i];
                        c2[i] = ((((c0i * 2) + c1i) / 3) | 0);
                        c3[i] = (((c0i + (c1i * 2)) / 3) | 0);
                    }
                    c2[3] = 255;
                    c3[3] = 255;
                } else {
                    for (i = 0; i < 3; i += 1) {
                        c2[i] = ((c0[i] + c1[i]) >> 1);
                        c3[i] = 0;
                    }
                    c2[3] = 255;
                    c3[3] = 0;
                }
            } else {
                c0 = decode565(col0, cache[0]);
                c1 = c0;
                c2 = c0;
                c3 = cache[1];
                for (i = 0; i < 4; i += 1) {
                    c3[i] = 0;
                }
            }

            var c = colorArray;
            c[0] = ((c0[3] >>> 7) | ((c0[2] & 0xf8) >>> 2) | ((c0[1] & 0xf8) << 3) | ((c0[0] & 0xf8) << 8));
            c[1] = ((c1[3] >>> 7) | ((c1[2] & 0xf8) >>> 2) | ((c1[1] & 0xf8) << 3) | ((c1[0] & 0xf8) << 8));
            c[2] = ((c2[3] >>> 7) | ((c2[2] & 0xf8) >>> 2) | ((c2[1] & 0xf8) << 3) | ((c2[0] & 0xf8) << 8));
            c[3] = ((c3[3] >>> 7) | ((c3[2] & 0xf8) >>> 2) | ((c3[1] & 0xf8) << 3) | ((c3[0] & 0xf8) << 8));

            // ((1 << 2) - 1) === 3;
            var row, dest, color;
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];
                dest[0] = c[(row) & 3];
                dest[1] = c[(row >> 2) & 3];
                dest[2] = c[(row >> 4) & 3];
                dest[3] = c[(row >> 6) & 3];
            }
            /*jshint bitwise: true*/
        }

        //var bpp = 2;
        var level;
        var width = srcWidth;
        var height = srcHeight;
        var numLevels = srcNumLevels;
        var numFaces = srcNumFaces;

        /*jshint bitwise: false*/
        var numPixels = 0;
        for (level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? (width >> 1) : 1);
            height = (height > 1 ? (height >> 1) : 1);
        }

        var dst = new Uint16Array(numPixels * 1 * numFaces);

        var src = 0, dest = 0;

        var color = [new Uint16Array(4), new Uint16Array(4), new Uint16Array(4), new Uint16Array(4)];
        var cache = [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)];
        var colorArray = new Uint16Array(4);

        for (var face = 0; face < numFaces; face += 1) {
            width = srcWidth;
            height = srcHeight;
            for (var n = 0; n < numLevels; n += 1) {
                var numColumns = (width > 4 ? 4 : width);
                var numLines = (height > 4 ? 4 : height);
                var heightInBlocks = ((height + 3) >> 2);
                var widthInBlocks = ((width + 3) >> 2);
                var desinationStride = (width * 1);
                var desinationLineStride = (numColumns * 1);
                var desinationBlockStride = (desinationStride * (numLines - 1));
                for (var y = 0; y < heightInBlocks; y += 1) {
                    for (var x = 0; x < widthInBlocks; x += 1) {
                        decodeDXT1Color(srcBuffer, src, color, cache, colorArray);
                        var destLine = dest;
                        for (var line = 0; line < numLines; line += 1) {
                            var colorLine = color[line];
                            var destRGBA = destLine;
                            for (var i = 0; i < numColumns; i += 1) {
                                dst[destRGBA] = colorLine[i];
                                destRGBA += 1;
                            }
                            destLine += desinationStride;
                        }
                        src += 8;
                        dest += desinationLineStride;
                    }
                    dest += desinationBlockStride;
                }

                width = (width > 1 ? (width >> 1) : 1);
                height = (height > 1 ? (height >> 1) : 1);
            }
        }

        /*jshint bitwise: true*/
        return dst;
    };

    DDSLoader.convertDXT3To4444 = function (srcBuffer, srcWidth, srcHeight, srcNumLevels, srcNumFaces) {
        function decodeDXT1Color(data, src, out, cache, colorArray) {
            function decode565(value, color) {
                /*jshint bitwise: false*/
                var r = ((value >> 11) & 31);
                var g = ((value >> 5) & 63);
                var b = ((value) & 31);
                color[0] = ((r << 3) | (r >> 2));
                color[1] = ((g << 2) | (g >> 4));
                color[2] = ((b << 3) | (b >> 2));
                color[3] = 255;

                /*jshint bitwise: true*/
                return color;
            }

            /*jshint bitwise: false*/
            var col0 = ((data[src + 1] << 8) | data[src]);
            src += 2;
            var col1 = ((data[src + 1] << 8) | data[src]);
            src += 2;

            var c0, c1, c2, c3, i;
            if (col0 !== col1) {
                c0 = decode565(col0, cache[0]);
                c1 = decode565(col1, cache[1]);
                c2 = cache[2];
                c3 = cache[3];

                if (col0 > col1) {
                    for (i = 0; i < 3; i += 1) {
                        var c0i = c0[i];
                        var c1i = c1[i];
                        c2[i] = ((((c0i * 2) + c1i) / 3) | 0);
                        c3[i] = (((c0i + (c1i * 2)) / 3) | 0);
                    }
                    c2[3] = 255;
                    c3[3] = 255;
                } else {
                    for (i = 0; i < 3; i += 1) {
                        c2[i] = ((c0[i] + c1[i]) >> 1);
                        c3[i] = 0;
                    }
                    c2[3] = 255;
                    c3[3] = 0;
                }
            } else {
                c0 = decode565(col0, cache[0]);
                c1 = c0;
                c2 = c0;
                c3 = cache[1];
                for (i = 0; i < 4; i += 1) {
                    c3[i] = 0;
                }
            }

            var c = colorArray;
            c[0] = c0;
            c[1] = c1;
            c[2] = c2;
            c[3] = c3;

            // ((1 << 2) - 1) === 3;
            var row, dest, color;
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];

                color = c[(row) & 3];
                dest[0][0] = color[0];
                dest[0][1] = color[1];
                dest[0][2] = color[2];
                dest[0][3] = color[3];

                color = c[(row >> 2) & 3];
                dest[1][0] = color[0];
                dest[1][1] = color[1];
                dest[1][2] = color[2];
                dest[1][3] = color[3];

                color = c[(row >> 4) & 3];
                dest[2][0] = color[0];
                dest[2][1] = color[1];
                dest[2][2] = color[2];
                dest[2][3] = color[3];

                color = c[(row >> 6) & 3];
                dest[3][0] = color[0];
                dest[3][1] = color[1];
                dest[3][2] = color[2];
                dest[3][3] = color[3];
            }
            /*jshint bitwise: true*/
        }

        function decodeDXT3Alpha(data, src, out) {
            for (var i = 0; i < 4; i += 1) {
                var row = ((data[src + 1] << 8) | data[src]);
                src += 2;
                var dest = out[i];
                if (row) {
                    dest[0][3] = ((row) & 15) * (255 / 15);
                    dest[1][3] = ((row >> 4) & 15) * (255 / 15);
                    dest[2][3] = ((row >> 8) & 15) * (255 / 15);
                    dest[3][3] = ((row >> 12) & 15) * (255 / 15);
                } else {
                    dest[0][3] = 0;
                    dest[1][3] = 0;
                    dest[2][3] = 0;
                    dest[3][3] = 0;
                }
            }
            /*jshint bitwise: true*/
        }

        //var bpp = 2;
        var level;
        var width = srcWidth;
        var height = srcHeight;
        var numLevels = srcNumLevels;
        var numFaces = srcNumFaces;

        /*jshint bitwise: false*/
        var numPixels = 0;
        for (level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? (width >> 1) : 1);
            height = (height > 1 ? (height >> 1) : 1);
        }

        var dst = new Uint16Array(numPixels * 1 * numFaces);

        var src = 0, dest = 0;

        var color = [
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)]
        ];
        var cache = [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)];
        var colorArray = new Array(4);

        for (var face = 0; face < numFaces; face += 1) {
            width = srcWidth;
            height = srcHeight;
            for (var n = 0; n < numLevels; n += 1) {
                var numColumns = (width > 4 ? 4 : width);
                var numLines = (height > 4 ? 4 : height);
                var heightInBlocks = ((height + 3) >> 2);
                var widthInBlocks = ((width + 3) >> 2);
                var desinationStride = (width * 1);
                var desinationLineStride = (numColumns * 1);
                var desinationBlockStride = (desinationStride * (numLines - 1));
                for (var y = 0; y < heightInBlocks; y += 1) {
                    for (var x = 0; x < widthInBlocks; x += 1) {
                        decodeDXT1Color(srcBuffer, (src + 8), color, cache, colorArray);
                        decodeDXT3Alpha(srcBuffer, src, color);
                        var destLine = dest;
                        for (var line = 0; line < numLines; line += 1) {
                            var colorLine = color[line];
                            var destRGBA = destLine;
                            for (var i = 0; i < numColumns; i += 1) {
                                var rgba = colorLine[i];
                                dst[destRGBA] = ((rgba[3] >>> 4) | (rgba[2] & 0xf0) | ((rgba[1] & 0xf0) << 4) | ((rgba[0] & 0xf0) << 8));
                                destRGBA += 1;
                            }
                            destLine += desinationStride;
                        }
                        src += 16;
                        dest += desinationLineStride;
                    }
                    dest += desinationBlockStride;
                }

                width = (width > 1 ? (width >> 1) : 1);
                height = (height > 1 ? (height >> 1) : 1);
            }
        }

        /*jshint bitwise: true*/
        return dst;
    };

    DDSLoader.convertDXT5To4444 = function (srcBuffer, srcWidth, srcHeight, srcNumLevels, srcNumFaces) {
        function decodeDXT1Color(data, src, out, cache, colorArray) {
            function decode565(value, color) {
                /*jshint bitwise: false*/
                var r = ((value >> 11) & 31);
                var g = ((value >> 5) & 63);
                var b = ((value) & 31);
                color[0] = ((r << 3) | (r >> 2));
                color[1] = ((g << 2) | (g >> 4));
                color[2] = ((b << 3) | (b >> 2));
                color[3] = 255;

                /*jshint bitwise: true*/
                return color;
            }

            /*jshint bitwise: false*/
            var col0 = ((data[src + 1] << 8) | data[src]);
            src += 2;
            var col1 = ((data[src + 1] << 8) | data[src]);
            src += 2;

            var c0, c1, c2, c3, i;
            if (col0 !== col1) {
                c0 = decode565(col0, cache[0]);
                c1 = decode565(col1, cache[1]);
                c2 = cache[2];
                c3 = cache[3];

                if (col0 > col1) {
                    for (i = 0; i < 3; i += 1) {
                        var c0i = c0[i];
                        var c1i = c1[i];
                        c2[i] = ((((c0i * 2) + c1i) / 3) | 0);
                        c3[i] = (((c0i + (c1i * 2)) / 3) | 0);
                    }
                    c2[3] = 255;
                    c3[3] = 255;
                } else {
                    for (i = 0; i < 3; i += 1) {
                        c2[i] = ((c0[i] + c1[i]) >> 1);
                        c3[i] = 0;
                    }
                    c2[3] = 255;
                    c3[3] = 0;
                }
            } else {
                c0 = decode565(col0, cache[0]);
                c1 = c0;
                c2 = c0;
                c3 = cache[1];
                for (i = 0; i < 4; i += 1) {
                    c3[i] = 0;
                }
            }

            var c = colorArray;
            c[0] = c0;
            c[1] = c1;
            c[2] = c2;
            c[3] = c3;

            // ((1 << 2) - 1) === 3;
            var row, dest, color;
            for (i = 0; i < 4; i += 1) {
                row = data[src + i];
                dest = out[i];

                color = c[(row) & 3];
                dest[0][0] = color[0];
                dest[0][1] = color[1];
                dest[0][2] = color[2];
                dest[0][3] = color[3];

                color = c[(row >> 2) & 3];
                dest[1][0] = color[0];
                dest[1][1] = color[1];
                dest[1][2] = color[2];
                dest[1][3] = color[3];

                color = c[(row >> 4) & 3];
                dest[2][0] = color[0];
                dest[2][1] = color[1];
                dest[2][2] = color[2];
                dest[2][3] = color[3];

                color = c[(row >> 6) & 3];
                dest[3][0] = color[0];
                dest[3][1] = color[1];
                dest[3][2] = color[2];
                dest[3][3] = color[3];
            }
            /*jshint bitwise: true*/
        }

        function decodeDXT5Alpha(data, src, out, alphaArray) {
            var a0 = data[src];
            src += 1;
            var a1 = data[src];
            src += 1;

            /*jshint bitwise: false*/
            var a = alphaArray;

            a[0] = a0;
            a[1] = a1;
            if (a0 > a1) {
                a[2] = ((((a0 * 6) + (a1 * 1)) / 7) | 0);
                a[3] = ((((a0 * 5) + (a1 * 2)) / 7) | 0);
                a[4] = ((((a0 * 4) + (a1 * 3)) / 7) | 0);
                a[5] = ((((a0 * 3) + (a1 * 4)) / 7) | 0);
                a[6] = ((((a0 * 2) + (a1 * 5)) / 7) | 0);
                a[7] = ((((a0 * 1) + (a1 * 6)) / 7) | 0);
            } else if (a0 < a1) {
                a[2] = ((((a0 * 4) + (a1 * 1)) / 5) | 0);
                a[3] = ((((a0 * 3) + (a1 * 2)) / 5) | 0);
                a[4] = ((((a0 * 2) + (a1 * 3)) / 5) | 0);
                a[5] = ((((a0 * 1) + (a1 * 4)) / 5) | 0);
                a[6] = 0;
                a[7] = 255;
            } else {
                a[2] = a0;
                a[3] = a0;
                a[4] = a0;
                a[5] = a0;
                a[6] = 0;
                a[7] = 255;
            }

            // ((1 << 3) - 1) === 7
            var dest;
            for (var i = 0; i < 2; i += 1) {
                var value = (data[src] | (data[src + 1] << 8) | (data[src + 2] << 16));
                src += 3;
                dest = out[(i * 2)];
                dest[0][3] = a[(value) & 7];
                dest[1][3] = a[(value >> 3) & 7];
                dest[2][3] = a[(value >> 6) & 7];
                dest[3][3] = a[(value >> 9) & 7];
                dest = out[(i * 2) + 1];
                dest[0][3] = a[(value >> 12) & 7];
                dest[1][3] = a[(value >> 15) & 7];
                dest[2][3] = a[(value >> 18) & 7];
                dest[3][3] = a[(value >> 21) & 7];
            }
            /*jshint bitwise: true*/
        }

        //var bpp = 2;
        var level;
        var width = srcWidth;
        var height = srcHeight;
        var numLevels = srcNumLevels;
        var numFaces = srcNumFaces;

        /*jshint bitwise: false*/
        var numPixels = 0;
        for (level = 0; level < numLevels; level += 1) {
            numPixels += (width * height);
            width = (width > 1 ? (width >> 1) : 1);
            height = (height > 1 ? (height >> 1) : 1);
        }

        var dst = new Uint16Array(numPixels * 1 * numFaces);

        var src = 0, dest = 0;

        var color = [
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)],
            [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)]
        ];
        var cache = [new Uint8Array(4), new Uint8Array(4), new Uint8Array(4), new Uint8Array(4)];
        var colorArray = new Array(4);
        var alphaArray = new Uint8Array(8);

        for (var face = 0; face < numFaces; face += 1) {
            width = srcWidth;
            height = srcHeight;
            for (var n = 0; n < numLevels; n += 1) {
                var numColumns = (width > 4 ? 4 : width);
                var numLines = (height > 4 ? 4 : height);
                var heightInBlocks = ((height + 3) >> 2);
                var widthInBlocks = ((width + 3) >> 2);
                var desinationStride = (width * 1);
                var desinationLineStride = (numColumns * 1);
                var desinationBlockStride = (desinationStride * (numLines - 1));
                for (var y = 0; y < heightInBlocks; y += 1) {
                    for (var x = 0; x < widthInBlocks; x += 1) {
                        decodeDXT1Color(srcBuffer, (src + 8), color, cache, colorArray);
                        decodeDXT5Alpha(srcBuffer, src, color, alphaArray);
                        var destLine = dest;
                        for (var line = 0; line < numLines; line += 1) {
                            var colorLine = color[line];
                            var destRGBA = destLine;
                            for (var i = 0; i < numColumns; i += 1) {
                                var rgba = colorLine[i];
                                dst[destRGBA] = ((rgba[3] >>> 4) | (rgba[2] & 0xf0) | ((rgba[1] & 0xf0) << 4) | ((rgba[0] & 0xf0) << 8));
                                destRGBA += 1;
                            }
                            destLine += desinationStride;
                        }
                        src += 16;
                        dest += desinationLineStride;
                    }
                    dest += desinationBlockStride;
                }

                width = (width > 1 ? (width >> 1) : 1);
                height = (height > 1 ? (height >> 1) : 1);
            }
        }

        /*jshint bitwise: true*/
        return dst;
    };

    DDSLoader.create = // Constructor function
    function (params) {
        var loader = new DDSLoader();
        loader.gd = params.gd;
        loader.onload = params.onload;
        loader.onerror = params.onerror;

        /*jshint bitwise: false*/
        function MAKEFOURCC(c0, c1, c2, c3) {
            return (c0.charCodeAt(0) + (c1.charCodeAt(0) * 256) + (c2.charCodeAt(0) * 65536) + (c3.charCodeAt(0) * 16777216));
        }

        /*jshint bitwise: true*/
        loader.FOURCC_ATI1 = MAKEFOURCC('A', 'T', 'I', '1');
        loader.FOURCC_ATI2 = MAKEFOURCC('A', 'T', 'I', '2');
        loader.FOURCC_RXGB = MAKEFOURCC('R', 'X', 'G', 'B');

        var src = params.src;
        if (src) {
            loader.src = src;
            var xhr;
            if (window.XMLHttpRequest) {
                xhr = new window.XMLHttpRequest();
            } else if (window.ActiveXObject) {
                xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
            } else {
                if (params.onerror) {
                    params.onerror(0);
                }
                return null;
            }

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                        var xhrStatus = xhr.status;
                        var xhrStatusText = xhr.status !== 0 && xhr.statusText || 'No connection';

                        if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                            xhrStatus = 200;
                        }

                        if (xhr.getAllResponseHeaders() === "") {
                            var noBody;
                            if (xhr.responseType === "arraybuffer") {
                                noBody = !xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                noBody = !xhr.mozResponseArrayBuffer;
                            } else {
                                noBody = !xhr.responseText;
                            }
                            if (noBody) {
                                if (loader.onerror) {
                                    loader.onerror(0);
                                }

                                // break circular reference
                                xhr.onreadystatechange = null;
                                xhr = null;
                                return;
                            }
                        }

                        if (xhrStatus === 200 || xhrStatus === 0) {
                            var buffer;
                            if (xhr.responseType === "arraybuffer") {
                                buffer = xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                buffer = xhr.mozResponseArrayBuffer;
                            } else {
                                /*jshint bitwise: false*/
                                var text = xhr.responseText;
                                var numChars = text.length;
                                buffer = [];
                                buffer.length = numChars;
                                for (var i = 0; i < numChars; i += 1) {
                                    buffer[i] = (text.charCodeAt(i) & 0xff);
                                }
                                /*jshint bitwise: true*/
                            }

                            loader.externalBuffer = false;
                            loader.processBytes(new Uint8Array(buffer), xhrStatus);
                        } else {
                            if (loader.onerror) {
                                loader.onerror(xhrStatus);
                            }
                        }
                    }

                    // break circular reference
                    xhr.onreadystatechange = null;
                    xhr = null;
                }
            };
            xhr.open("GET", params.src, true);
            if (typeof xhr.responseType === "string" || (xhr.hasOwnProperty && xhr.hasOwnProperty("responseType"))) {
                xhr.responseType = "arraybuffer";
            } else if (xhr.overrideMimeType) {
                xhr.overrideMimeType("text/plain; charset=x-user-defined");
            } else {
                xhr.setRequestHeader("Content-Type", "text/plain; charset=x-user-defined");
            }
            xhr.send(null);
        } else {
            loader.externalBuffer = true;
            loader.processBytes((params.data), 0);
        }

        return loader;
    };

    DDSLoader.destroy = function () {
        var maxNumWorkers = this.maxNumWorkers;
        if (maxNumWorkers) {
            this.workerQueues = null;
            var workers = this.workers;
            if (workers) {
                this.workers = null;
                var n;
                for (n = 0; n < maxNumWorkers; n += 1) {
                    workers[n].terminate();
                }
            }
        }
    };
    DDSLoader.version = 1;

    DDSLoader.maxNumWorkers = (typeof Worker !== "undefined" && typeof Blob !== "undefined" && (typeof URL !== "undefined" || typeof window['webkitURL'] !== "undefined") ? 4 : 0);
    DDSLoader.WorkerCommand = {
        DXT1: 0,
        DXT1A: 1,
        DXT3: 2,
        DXT5: 3
    };
    DDSLoader.workerQueues = null;
    DDSLoader.workers = null;
    return DDSLoader;
})();

// surface description flags
DDSLoader.prototype.DDSF_CAPS = 0x00000001;
DDSLoader.prototype.DDSF_HEIGHT = 0x00000002;
DDSLoader.prototype.DDSF_WIDTH = 0x00000004;
DDSLoader.prototype.DDSF_PITCH = 0x00000008;
DDSLoader.prototype.DDSF_PIXELFORMAT = 0x00001000;
DDSLoader.prototype.DDSF_MIPMAPCOUNT = 0x00020000;
DDSLoader.prototype.DDSF_LINEARSIZE = 0x00080000;
DDSLoader.prototype.DDSF_DEPTH = 0x00800000;

// pixel format flags
DDSLoader.prototype.DDSF_ALPHAPIXELS = 0x00000001;
DDSLoader.prototype.DDSF_FOURCC = 0x00000004;
DDSLoader.prototype.DDSF_RGB = 0x00000040;
DDSLoader.prototype.DDSF_RGBA = 0x00000041;

// dwCaps1 flags
DDSLoader.prototype.DDSF_COMPLEX = 0x00000008;
DDSLoader.prototype.DDSF_TEXTURE = 0x00001000;
DDSLoader.prototype.DDSF_MIPMAP = 0x00400000;

// dwCaps2 flags
DDSLoader.prototype.DDSF_CUBEMAP = 0x00000200;
DDSLoader.prototype.DDSF_CUBEMAP_POSITIVEX = 0x00000400;
DDSLoader.prototype.DDSF_CUBEMAP_NEGATIVEX = 0x00000800;
DDSLoader.prototype.DDSF_CUBEMAP_POSITIVEY = 0x00001000;
DDSLoader.prototype.DDSF_CUBEMAP_NEGATIVEY = 0x00002000;
DDSLoader.prototype.DDSF_CUBEMAP_POSITIVEZ = 0x00004000;
DDSLoader.prototype.DDSF_CUBEMAP_NEGATIVEZ = 0x00008000;
DDSLoader.prototype.DDSF_CUBEMAP_ALL_FACES = 0x0000FC00;
DDSLoader.prototype.DDSF_VOLUME = 0x00200000;

// compressed texture types
DDSLoader.prototype.FOURCC_UNKNOWN = 0;

DDSLoader.prototype.FOURCC_R8G8B8 = 20;
DDSLoader.prototype.FOURCC_A8R8G8B8 = 21;
DDSLoader.prototype.FOURCC_X8R8G8B8 = 22;
DDSLoader.prototype.FOURCC_R5G6B5 = 23;
DDSLoader.prototype.FOURCC_X1R5G5B5 = 24;
DDSLoader.prototype.FOURCC_A1R5G5B5 = 25;
DDSLoader.prototype.FOURCC_A4R4G4B4 = 26;
DDSLoader.prototype.FOURCC_R3G3B2 = 27;
DDSLoader.prototype.FOURCC_A8 = 28;
DDSLoader.prototype.FOURCC_A8R3G3B2 = 29;
DDSLoader.prototype.FOURCC_X4R4G4B4 = 30;
DDSLoader.prototype.FOURCC_A2B10G10R10 = 31;
DDSLoader.prototype.FOURCC_A8B8G8R8 = 32;
DDSLoader.prototype.FOURCC_X8B8G8R8 = 33;
DDSLoader.prototype.FOURCC_G16R16 = 34;
DDSLoader.prototype.FOURCC_A2R10G10B10 = 35;
DDSLoader.prototype.FOURCC_A16B16G16R16 = 36;

DDSLoader.prototype.FOURCC_L8 = 50;
DDSLoader.prototype.FOURCC_A8L8 = 51;
DDSLoader.prototype.FOURCC_A4L4 = 52;
DDSLoader.prototype.FOURCC_DXT1 = 0x31545844;
DDSLoader.prototype.FOURCC_DXT2 = 0x32545844;
DDSLoader.prototype.FOURCC_DXT3 = 0x33545844;
DDSLoader.prototype.FOURCC_DXT4 = 0x34545844;
DDSLoader.prototype.FOURCC_DXT5 = 0x35545844;

DDSLoader.prototype.FOURCC_D16_LOCKABLE = 70;
DDSLoader.prototype.FOURCC_D32 = 71;
DDSLoader.prototype.FOURCC_D24X8 = 77;
DDSLoader.prototype.FOURCC_D16 = 80;

DDSLoader.prototype.FOURCC_D32F_LOCKABLE = 82;

DDSLoader.prototype.FOURCC_L16 = 81;

// Floating point surface formats
// s10e5 formats (16-bits per channel)
DDSLoader.prototype.FOURCC_R16F = 111;
DDSLoader.prototype.FOURCC_G16R16F = 112;
DDSLoader.prototype.FOURCC_A16B16G16R16F = 113;

// IEEE s23e8 formats (32-bits per channel)
DDSLoader.prototype.FOURCC_R32F = 114;
DDSLoader.prototype.FOURCC_G32R32F = 115;
DDSLoader.prototype.FOURCC_A32B32G32R32F = 116;

DDSLoader.prototype.BGRPIXELFORMAT_B5G6R5 = 1;
DDSLoader.prototype.BGRPIXELFORMAT_B8G8R8A8 = 2;
DDSLoader.prototype.BGRPIXELFORMAT_B8G8R8 = 3;

// Copyright (c) 2011-2013 Turbulenz Limited
/*global TurbulenzEngine*/
/*global TGALoader*/
/*global DDSLoader*/
/*global TARLoader*/
/*global Int8Array*/
/*global Int16Array*/
/*global Int32Array*/
/*global Uint8Array*/
/*global Uint8ClampedArray*/
/*global Uint16Array*/
/*global Uint32Array*/
/*global Float32Array*/
/*global ArrayBuffer*/
/*global DataView*/
/*global window*/
/*global debug*/

;

// -----------------------------------------------------------------------------
var TZWebGLTexture = (function () {
    function TZWebGLTexture() {
    }
    TZWebGLTexture.prototype.setData = function (data, face, level, x, y, w, h) {
        var gd = this.gd;
        var target = this.target;
        gd.bindTexture(target, this.glTexture);
        /* debug.assert(arguments.length === 1 || 3 <= arguments.length); */
        if (3 <= arguments.length) {
            if (x === undefined) {
                x = 0;
            }
            if (y === undefined) {
                y = 0;
            }
            if (w === undefined) {
                w = (this.width - x);
            }
            if (h === undefined) {
                h = (this.height - y);
            }
            this.updateSubData(data, face, level, x, y, w, h);
        } else {
            this.updateData(data);
        }
        gd.bindTexture(target, null);
    };

    // Internal
    TZWebGLTexture.prototype.createGLTexture = function (data) {
        var gd = this.gd;
        var gl = gd.gl;

        var target;
        if (this.cubemap) {
            target = gl.TEXTURE_CUBE_MAP;
        } else if (this.depth > 1) {
            //target = gl.TEXTURE_3D;
            // 3D textures are not supported yet
            return false;
        } else {
            target = gl.TEXTURE_2D;
        }
        this.target = target;

        var gltex = gl.createTexture();
        this.glTexture = gltex;

        gd.bindTexture(target, gltex);

        gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, gl.LINEAR);

        if (this.mipmaps) {
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR_MIPMAP_NEAREST);
        } else {
            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
        }

        gl.texParameteri(target, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
        gl.texParameteri(target, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);

        this.updateData(data);

        gd.bindTexture(target, null);

        return true;
    };

    TZWebGLTexture.prototype.convertDataToRGBA = function (gl, data, internalFormat, gltype, srcStep) {
        var numPixels = (data.length / srcStep);
        var rgbaData = new Uint8Array(numPixels * 4);
        var offset = 0;
        var n, value, r, g, b, a;
        if (internalFormat === gl.LUMINANCE) {
            /* debug.assert(srcStep === 1); */
            for (n = 0; n < numPixels; n += 1, offset += 4) {
                r = data[n];
                rgbaData[offset] = r;
                rgbaData[offset + 1] = r;
                rgbaData[offset + 2] = r;
                rgbaData[offset + 3] = 0xff;
            }
        } else if (internalFormat === gl.ALPHA) {
            /* debug.assert(srcStep === 1); */
            for (n = 0; n < numPixels; n += 1, offset += 4) {
                a = data[n];
                rgbaData[offset] = 0xff;
                rgbaData[offset + 1] = 0xff;
                rgbaData[offset + 2] = 0xff;
                rgbaData[offset + 3] = a;
            }
        } else if (internalFormat === gl.LUMINANCE_ALPHA) {
            /* debug.assert(srcStep === 2); */
            for (n = 0; n < numPixels; n += 2, offset += 4) {
                r = data[n];
                a = data[n + 1];
                rgbaData[offset] = r;
                rgbaData[offset + 1] = r;
                rgbaData[offset + 2] = r;
                rgbaData[offset + 3] = a;
            }
        } else if (gltype === gl.UNSIGNED_SHORT_5_6_5) {
            /* debug.assert(srcStep === 1); */
            for (n = 0; n < numPixels; n += 1, offset += 4) {
                value = data[n];
                r = ((value >> 11) & 31);
                g = ((value >> 5) & 63);
                b = ((value) & 31);
                rgbaData[offset] = ((r << 3) | (r >> 2));
                rgbaData[offset + 1] = ((g << 2) | (g >> 4));
                rgbaData[offset + 2] = ((b << 3) | (b >> 2));
                rgbaData[offset + 3] = 0xff;
            }
        } else if (gltype === gl.UNSIGNED_SHORT_5_5_5_1) {
            /* debug.assert(srcStep === 1); */
            for (n = 0; n < numPixels; n += 1, offset += 4) {
                value = data[n];
                r = ((value >> 11) & 31);
                g = ((value >> 6) & 31);
                b = ((value >> 1) & 31);
                a = ((value) & 1);
                rgbaData[offset] = ((r << 3) | (r >> 2));
                rgbaData[offset + 1] = ((g << 3) | (g >> 2));
                rgbaData[offset + 2] = ((b << 3) | (b >> 2));
                rgbaData[offset + 3] = (a ? 0xff : 0);
            }
        } else if (gltype === gl.UNSIGNED_SHORT_4_4_4_4) {
            /* debug.assert(srcStep === 1); */
            for (n = 0; n < numPixels; n += 1, offset += 4) {
                value = data[n];
                r = ((value >> 12) & 15);
                g = ((value >> 8) & 15);
                b = ((value >> 4) & 15);
                a = ((value) & 15);
                rgbaData[offset] = ((r << 4) | r);
                rgbaData[offset + 1] = ((g << 4) | g);
                rgbaData[offset + 2] = ((b << 4) | b);
                rgbaData[offset + 3] = ((a << 4) | a);
            }
        }
        return rgbaData;
    };

    TZWebGLTexture.prototype.updateData = function (data) {
        var gd = this.gd;
        var gl = gd.gl;

        function log2(a) {
            return Math.floor(Math.log(a) / Math.LN2);
        }

        var numLevels, generateMipMaps;
        if (this.mipmaps) {
            if (data instanceof Image) {
                numLevels = 1;
                generateMipMaps = true;
            } else {
                numLevels = (1 + Math.max(log2(this.width), log2(this.height)));
                generateMipMaps = false;
            }
        } else {
            numLevels = 1;
            generateMipMaps = false;
        }

        var format = this.format;
        var internalFormat, gltype, srcStep, bufferData = null;
        var compressedTexturesExtension;

        if (format === gd.PIXELFORMAT_A8) {
            internalFormat = gl.ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 1;
            if (data && !data.src) {
                if (data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_L8) {
            internalFormat = gl.LUMINANCE;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 1;
            if (data && !data.src) {
                if (data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_L8A8) {
            internalFormat = gl.LUMINANCE_ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 2;
            if (data && !data.src) {
                if (data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_R5G5B5A1) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_5_5_5_1;
            srcStep = 1;
            if (data && !data.src) {
                if (data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_R5G6B5) {
            internalFormat = gl.RGB;
            gltype = gl.UNSIGNED_SHORT_5_6_5;
            srcStep = 1;
            if (data && !data.src) {
                if (data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_R4G4B4A4) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_4_4_4_4;
            srcStep = 1;
            if (data && !data.src) {
                if (data instanceof Uint16Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint16Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_R8G8B8A8) {
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 4;
            if (data && !data.src) {
                if (data instanceof Uint8Array) {
                    if (typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                        bufferData = new Uint8Array(data.buffer);
                    } else {
                        bufferData = data;
                    }
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_R8G8B8) {
            internalFormat = gl.RGB;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 3;
            if (data && !data.src) {
                if (data instanceof Uint8Array) {
                    if (typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                        bufferData = new Uint8Array(data.buffer);
                    } else {
                        bufferData = data;
                    }
                } else {
                    bufferData = new Uint8Array(data);
                }
            }
        } else if (format === gd.PIXELFORMAT_D24S8) {
            //internalFormat = gl.DEPTH24_STENCIL8_EXT;
            //gltype = gl.UNSIGNED_INT_24_8_EXT;
            //internalFormat = gl.DEPTH_COMPONENT;
            internalFormat = gl.DEPTH_STENCIL;
            gltype = gl.UNSIGNED_INT;
            srcStep = 1;
            if (data && !data.src) {
                bufferData = new Uint32Array(data);
            }
        } else if (format === gd.PIXELFORMAT_DXT1 || format === gd.PIXELFORMAT_DXT3 || format === gd.PIXELFORMAT_DXT5) {
            compressedTexturesExtension = gd.compressedTexturesExtension;
            if (compressedTexturesExtension) {
                if (format === gd.PIXELFORMAT_DXT1) {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
                    srcStep = 8;
                } else if (format === gd.PIXELFORMAT_DXT3) {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                    srcStep = 16;
                } else {
                    internalFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                    srcStep = 16;
                }

                if (internalFormat === undefined) {
                    return;
                }

                if (data && !data.src) {
                    if (data instanceof Uint8Array) {
                        bufferData = data;
                    } else {
                        bufferData = new Uint8Array(data);
                    }
                }
            } else {
                return;
            }
        } else {
            return;
        }

        if (gd.fixIE && ((internalFormat !== gl.RGBA && internalFormat !== gl.RGB) || (gltype !== gl.UNSIGNED_BYTE && gltype !== gl.FLOAT))) {
            if (bufferData) {
                bufferData = this.convertDataToRGBA(gl, bufferData, internalFormat, gltype, srcStep);
            }
            internalFormat = gl.RGBA;
            gltype = gl.UNSIGNED_BYTE;
            srcStep = 4;
        }

        var w = this.width, h = this.height, offset = 0, target, n, levelSize, levelData;
        if (this.cubemap) {
            if (data && data instanceof WebGLVideo) {
                return;
            }

            target = gl.TEXTURE_CUBE_MAP;

            for (var f = 0; f < 6; f += 1) {
                var faceTarget = (gl.TEXTURE_CUBE_MAP_POSITIVE_X + f);
                for (n = 0; n < numLevels; n += 1) {
                    if (compressedTexturesExtension) {
                        levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep);
                        if (bufferData) {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                        } else {
                            levelData = new Uint8Array(levelSize);
                        }
                        if (gd.WEBGL_compressed_texture_s3tc) {
                            gl.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, levelData);
                        } else {
                            compressedTexturesExtension.compressedTexImage2D(faceTarget, n, internalFormat, w, h, 0, levelData);
                        }
                    } else {
                        levelSize = (w * h * srcStep);
                        if (bufferData) {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                            gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                        } else if (data) {
                            gl.texImage2D(faceTarget, n, internalFormat, internalFormat, gltype, data);
                        } else {
                            if (gltype === gl.UNSIGNED_SHORT_5_6_5 || gltype === gl.UNSIGNED_SHORT_5_5_5_1 || gltype === gl.UNSIGNED_SHORT_4_4_4_4) {
                                levelData = new Uint16Array(levelSize);
                            } else {
                                levelData = new Uint8Array(levelSize);
                            }
                            gl.texImage2D(faceTarget, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                        }
                    }
                    offset += levelSize;
                    if (bufferData && bufferData.length <= offset) {
                        bufferData = null;
                        data = null;
                        if (0 === n && 1 < numLevels) {
                            generateMipMaps = true;
                            break;
                        }
                    }
                    w = (w > 1 ? Math.floor(w / 2) : 1);
                    h = (h > 1 ? Math.floor(h / 2) : 1);
                }
                w = this.width;
                h = this.height;
            }
        } else if (data && data instanceof WebGLVideo) {
            target = gl.TEXTURE_2D;
            gl.texImage2D(target, 0, internalFormat, internalFormat, gltype, data.video);
        } else {
            target = gl.TEXTURE_2D;

            for (n = 0; n < numLevels; n += 1) {
                if (compressedTexturesExtension) {
                    levelSize = (Math.floor((w + 3) / 4) * Math.floor((h + 3) / 4) * srcStep);
                    if (bufferData) {
                        if (numLevels === 1) {
                            levelData = bufferData;
                        } else {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                        }
                    } else {
                        levelData = new Uint8Array(levelSize);
                    }
                    if (gd.WEBGL_compressed_texture_s3tc) {
                        gl.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData);
                    } else {
                        compressedTexturesExtension.compressedTexImage2D(target, n, internalFormat, w, h, 0, levelData);
                    }
                } else {
                    levelSize = (w * h * srcStep);
                    if (bufferData) {
                        if (numLevels === 1) {
                            levelData = bufferData;
                        } else {
                            levelData = bufferData.subarray(offset, (offset + levelSize));
                        }
                        gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                    } else if (data) {
                        gl.texImage2D(target, n, internalFormat, internalFormat, gltype, data);
                    } else {
                        if (gltype === gl.UNSIGNED_SHORT_5_6_5 || gltype === gl.UNSIGNED_SHORT_5_5_5_1 || gltype === gl.UNSIGNED_SHORT_4_4_4_4) {
                            levelData = new Uint16Array(levelSize);
                        } else {
                            levelData = new Uint8Array(levelSize);
                        }
                        gl.texImage2D(target, n, internalFormat, w, h, 0, internalFormat, gltype, levelData);
                    }
                }
                offset += levelSize;
                if (bufferData && bufferData.length <= offset) {
                    bufferData = null;
                    data = null;
                    if (0 === n && 1 < numLevels) {
                        generateMipMaps = true;
                        break;
                    }
                }
                w = (w > 1 ? Math.floor(w / 2) : 1);
                h = (h > 1 ? Math.floor(h / 2) : 1);
            }
        }

        if (generateMipMaps) {
            gl.generateMipmap(target);
        }
    };

    TZWebGLTexture.prototype.updateSubData = function (data, face, level, x, y, w, h) {
        /* debug.assert(data); */
        /* debug.assert(face === 0 || (this.cubemap && face < 6)); */
        /* debug.assert(0 <= x && (x + w) <= this.width); */
        /* debug.assert(0 <= y && (y + h) <= this.height); */
        var gd = this.gd;
        var gl = gd.gl;

        var format = this.format;
        var glformat, gltype, bufferData;
        var compressedTexturesExtension;

        if (format === gd.PIXELFORMAT_A8) {
            glformat = gl.ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            if (data instanceof Uint8Array) {
                bufferData = data;
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gd.PIXELFORMAT_L8) {
            glformat = gl.LUMINANCE;
            gltype = gl.UNSIGNED_BYTE;
            if (data instanceof Uint8Array) {
                bufferData = data;
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gd.PIXELFORMAT_L8A8) {
            glformat = gl.LUMINANCE_ALPHA;
            gltype = gl.UNSIGNED_BYTE;
            if (data instanceof Uint8Array) {
                bufferData = data;
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gd.PIXELFORMAT_R5G5B5A1) {
            glformat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_5_5_5_1;
            if (data instanceof Uint16Array) {
                bufferData = data;
            } else {
                bufferData = new Uint16Array(data);
            }
        } else if (format === gd.PIXELFORMAT_R5G6B5) {
            glformat = gl.RGB;
            gltype = gl.UNSIGNED_SHORT_5_6_5;
            if (data instanceof Uint16Array) {
                bufferData = data;
            } else {
                bufferData = new Uint16Array(data);
            }
        } else if (format === gd.PIXELFORMAT_R4G4B4A4) {
            glformat = gl.RGBA;
            gltype = gl.UNSIGNED_SHORT_4_4_4_4;
            if (data instanceof Uint16Array) {
                bufferData = data;
            } else {
                bufferData = new Uint16Array(data);
            }
        } else if (format === gd.PIXELFORMAT_R8G8B8A8) {
            glformat = gl.RGBA;
            gltype = gl.UNSIGNED_BYTE;
            if (data instanceof Uint8Array) {
                if (typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                    bufferData = new Uint8Array(data.buffer);
                } else {
                    bufferData = data;
                }
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gd.PIXELFORMAT_R8G8B8) {
            glformat = gl.RGB;
            gltype = gl.UNSIGNED_BYTE;
            if (data instanceof Uint8Array) {
                if (typeof Uint8ClampedArray !== "undefined" && data instanceof Uint8ClampedArray) {
                    bufferData = new Uint8Array(data.buffer);
                } else {
                    bufferData = data;
                }
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gd.PIXELFORMAT_DXT1 || format === gd.PIXELFORMAT_DXT3 || format === gd.PIXELFORMAT_DXT5) {
            compressedTexturesExtension = gd.compressedTexturesExtension;
            if (compressedTexturesExtension) {
                if (format === gd.PIXELFORMAT_DXT1) {
                    glformat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
                } else if (format === gd.PIXELFORMAT_DXT3) {
                    glformat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                } else {
                    glformat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                }

                if (data instanceof Uint8Array) {
                    bufferData = data;
                } else {
                    bufferData = new Uint8Array(data);
                }
            } else {
                return;
            }
        } else {
            return;
        }

        var target;
        if (this.cubemap) {
            if (data instanceof WebGLVideo) {
                return;
            }

            target = (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face);
        } else if (data instanceof WebGLVideo) {
            target = gl.TEXTURE_2D;

            // width and height are taken from video
            gl.texSubImage2D(target, level, x, y, glformat, gltype, data.video);
            return;
        } else {
            target = gl.TEXTURE_2D;
        }

        if (compressedTexturesExtension) {
            if (gd.WEBGL_compressed_texture_s3tc) {
                gl.compressedTexSubImage2D(target, level, x, y, w, h, glformat, bufferData);
            } else {
                compressedTexturesExtension.compressedTexSubImage2D(target, level, x, y, w, h, glformat, bufferData);
            }
        } else {
            gl.texSubImage2D(target, level, x, y, w, h, glformat, gltype, bufferData);
        }
    };

    TZWebGLTexture.prototype.updateMipmaps = function (face) {
        if (this.mipmaps) {
            if (this.depth > 1) {
                (TurbulenzEngine).callOnError("3D texture mipmap generation unsupported");
                return;
            }

            if (this.cubemap && face !== 5) {
                return;
            }

            var gd = this.gd;
            var gl = gd.gl;

            var target = this.target;
            gd.bindTexture(target, this.glTexture);
            gl.generateMipmap(target);
            gd.bindTexture(target, null);
        }
    };

    TZWebGLTexture.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var glTexture = this.glTexture;
            if (glTexture) {
                var gl = gd.gl;
                if (gl) {
                    gd.unbindTexture(glTexture);
                    gl.deleteTexture(glTexture);
                }
                delete this.glTexture;
            }

            delete this.sampler;
            delete this.gd;
        }
    };

    TZWebGLTexture.prototype.typedArrayIsValid = function (typedArray) {
        var gd = this.gd;
        var format = this.format;

        if (gd) {
            if ((format === gd.PIXELFORMAT_A8) || (format === gd.PIXELFORMAT_L8) || (format === gd.PIXELFORMAT_S8)) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === this.width * this.height * this.depth);
            }
            if (format === gd.PIXELFORMAT_L8A8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 2 * this.width * this.height * this.depth);
            }
            if (format === gd.PIXELFORMAT_R8G8B8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 3 * this.width * this.height * this.depth);
            }
            if (format === gd.PIXELFORMAT_R8G8B8A8) {
                return ((typedArray instanceof Uint8Array) || (typeof Uint8ClampedArray !== "undefined" && typedArray instanceof Uint8ClampedArray)) && (typedArray.length === 4 * this.width * this.height * this.depth);
            }
            if ((format === gd.PIXELFORMAT_R5G5B5A1) || (format === gd.PIXELFORMAT_R5G6B5) || (format === gd.PIXELFORMAT_R4G4B4A4)) {
                return (typedArray instanceof Uint16Array) && (typedArray.length === this.width * this.height * this.depth);
            }
        }
        return false;
    };

    TZWebGLTexture.create = function (gd, params) {
        var tex = new TZWebGLTexture();
        tex.gd = gd;
        tex.mipmaps = params.mipmaps;
        tex.dynamic = params.dynamic;
        tex.renderable = params.renderable;
        tex.id = ++gd.counters.textures;

        var src = params.src;
        if (src) {
            tex.name = params.name || src;
            var extension;
            var data = params.data;
            if (data) {
                if (data[0] === 137 && data[1] === 80 && data[2] === 78 && data[3] === 71) {
                    extension = '.png';
                } else if (data[0] === 255 && data[1] === 216 && data[2] === 255 && (data[3] === 224 || data[3] === 225)) {
                    extension = '.jpg';
                } else if (data[0] === 68 && data[1] === 68 && data[2] === 83 && data[3] === 32) {
                    extension = '.dds';
                } else {
                    extension = src.slice(-4);
                }
            } else {
                extension = src.slice(-4);
            }

            if (extension === '.dds' || extension === '.tga') {
                if (extension === '.tga' && typeof TGALoader !== 'undefined') {
                    var tgaParams = {
                        gd: gd,
                        onload: function tgaLoadedFn(data, width, height, format, status) {
                            tex.width = width;
                            tex.height = height;
                            tex.depth = 1;
                            tex.format = format;
                            tex.cubemap = false;
                            var result = tex.createGLTexture(data);
                            if (params.onload) {
                                params.onload(result ? tex : null, status);
                            }
                        },
                        onerror: function tgaFailedFn(status) {
                            tex.failed = true;
                            if (params.onload) {
                                params.onload(null, status);
                            }
                        },
                        data: undefined,
                        src: undefined
                    };
                    if (data) {
                        tgaParams.data = data;
                    } else {
                        tgaParams.src = src;
                    }
                    TGALoader.create(tgaParams);
                    return tex;
                } else if (extension === '.dds' && typeof DDSLoader !== 'undefined') {
                    var ddsParams = {
                        gd: gd,
                        onload: function ddsLoadedFn(data, width, height, format, numLevels, cubemap, depth, status) {
                            tex.width = width;
                            tex.height = height;
                            tex.format = format;
                            tex.cubemap = cubemap;
                            tex.depth = depth;
                            if (1 < numLevels) {
                                if (!tex.mipmaps) {
                                    tex.mipmaps = true;
                                    /* debug.log("Mipmap levels provided for texture created without mipmaps enabled: " + tex.name); */
                                }
                            }
                            var result = tex.createGLTexture(data);
                            if (params.onload) {
                                params.onload(result ? tex : null, status);
                            }
                        },
                        onerror: function ddsFailedFn(status) {
                            tex.failed = true;
                            if (params.onload) {
                                params.onload(null, status);
                            }
                        },
                        data: undefined,
                        src: undefined
                    };
                    if (data) {
                        ddsParams.data = data;
                    } else {
                        ddsParams.src = src;
                    }
                    DDSLoader.create(ddsParams);
                    return tex;
                } else {
                    (TurbulenzEngine).callOnError('Missing image loader required for ' + src);

                    tex = TZWebGLTexture.create(gd, {
                        name: (params.name || src),
                        width: 2,
                        height: 2,
                        depth: 1,
                        format: 'R8G8B8A8',
                        cubemap: false,
                        mipmaps: params.mipmaps,
                        dynamic: params.dynamic,
                        renderable: params.renderable,
                        data: [
                            255,
                            20,
                            147,
                            255,
                            255,
                            0,
                            0,
                            255,
                            255,
                            255,
                            255,
                            255,
                            255,
                            20,
                            147,
                            255
                        ]
                    });

                    if (params.onload) {
                        if (TurbulenzEngine) {
                            TurbulenzEngine.setTimeout(function () {
                                params.onload(tex, 200);
                            }, 0);
                        } else {
                            window.setTimeout(function () {
                                params.onload(tex, 200);
                            }, 0);
                        }
                    }
                    return tex;
                }
            }

            var img = new Image();
            var imageLoaded = function imageLoadedFn() {
                tex.width = img.width;
                tex.height = img.height;
                tex.depth = 1;
                tex.format = gd.PIXELFORMAT_R8G8B8A8;
                tex.cubemap = false;
                var result = tex.createGLTexture(img);
                if (params.onload) {
                    params.onload(result ? tex : null, 200);
                }
            };
            img.onload = imageLoaded;
            img.onerror = function imageFailedFn() {
                tex.failed = true;
                if (params.onload) {
                    params.onload(null);
                }
            };
            if (data) {
                if (typeof Blob !== "undefined" && typeof URL !== "undefined" && URL.createObjectURL) {
                    var dataBlob;
                    if (extension === '.jpg' || extension === '.jpeg') {
                        dataBlob = new Blob([data], { type: "image/jpeg" });
                    } else if (extension === '.png') {
                        dataBlob = new Blob([data], { type: "image/png" });
                    }
                    /* debug.assert(data.length === dataBlob.size, "Blob constructor does not support typed arrays."); */
                    img.onload = function blobImageLoadedFn() {
                        imageLoaded();
                        URL.revokeObjectURL(img.src);
                        dataBlob = null;
                    };
                    src = URL.createObjectURL(dataBlob);
                } else {
                    if (extension === '.jpg' || extension === '.jpeg') {
                        src = 'data:image/jpeg;base64,' + (TurbulenzEngine).base64Encode(data);
                    } else if (extension === '.png') {
                        src = 'data:image/png;base64,' + (TurbulenzEngine).base64Encode(data);
                    }
                }
                img.src = src;
            } else if (typeof URL !== "undefined" && URL.createObjectURL) {
                var xhr = new XMLHttpRequest();
                xhr.onreadystatechange = function () {
                    if (xhr.readyState === 4) {
                        if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                            var xhrStatus = xhr.status;

                            if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                                xhrStatus = 200;
                            }

                            if (xhr.getAllResponseHeaders() === "" && !xhr.response) {
                                if (params.onload) {
                                    params.onload(null, 0);
                                }
                            } else {
                                if (xhrStatus === 200 || xhrStatus === 0) {
                                    var blob = xhr.response;
                                    img.onload = function blobImageLoadedFn() {
                                        imageLoaded();
                                        URL.revokeObjectURL(img.src);
                                        blob = null;
                                    };
                                    img.src = URL.createObjectURL(blob);
                                } else {
                                    params.onload(null, xhrStatus);
                                }
                            }
                            xhr.onreadystatechange = null;
                            xhr = null;
                        }
                        return tex;
                    }
                };
                xhr.open('GET', src, true);
                xhr.responseType = 'blob';
                xhr.send();
            } else {
                img.crossOrigin = 'anonymous';
                img.src = src;
            }
        } else {
            if ("" === src && params.onload) {
                // Assume the caller intended to pass in a valid url.
                return null;
            }

            var format = params.format;
            if (typeof format === 'string') {
                format = gd['PIXELFORMAT_' + format];
            }

            tex.width = params.width;
            tex.height = params.height;
            tex.depth = params.depth;
            tex.format = format;
            tex.cubemap = params.cubemap;
            tex.name = params.name;

            var result = tex.createGLTexture(params.data);
            if (!result) {
                tex = null;
            }

            if (params.renderable) {
                if (gd.PIXELFORMAT_D16 === format) {
                    tex.glDepthAttachment = gd.gl.DEPTH_ATTACHMENT;
                } else if (gd.PIXELFORMAT_D24S8 === format) {
                    tex.glDepthAttachment = gd.gl.DEPTH_STENCIL_ATTACHMENT;
                }
            }

            if (params.onload) {
                params.onload(tex, 200);
            }
        }

        return tex;
    };
    TZWebGLTexture.version = 1;
    return TZWebGLTexture;
})();

//
// WebGLVideo
//
var WebGLVideo = (function () {
    function WebGLVideo() {
    }
    // Public API
    WebGLVideo.prototype.play = function (seek) {
        var video = this.video;

        if (!this.playing) {
            this.playing = true;
            this.paused = false;
        }

        if (seek === undefined) {
            seek = 0;
        }

        if (0.01 < Math.abs(video.currentTime - seek)) {
            try  {
                video.currentTime = seek;
            } catch (e) {
                // There does not seem to be any reliable way of seeking
            }
        }

        video.play();

        return true;
    };

    WebGLVideo.prototype.stop = function () {
        var playing = this.playing;
        if (playing) {
            this.playing = false;
            this.paused = false;

            var video = this.video;
            video.pause();
            video.currentTime = 0;
        }

        return playing;
    };

    WebGLVideo.prototype.pause = function () {
        if (this.playing) {
            if (!this.paused) {
                this.paused = true;

                this.video.pause();
            }

            return true;
        }

        return false;
    };

    WebGLVideo.prototype.resume = function (seek) {
        if (this.paused) {
            this.paused = false;

            var video = this.video;

            if (seek !== undefined) {
                if (0.01 < Math.abs(video.currentTime - seek)) {
                    try  {
                        video.currentTime = seek;
                    } catch (e) {
                        // There does not seem to be any reliable way of seeking
                    }
                }
            }

            video.play();

            return true;
        }

        return false;
    };

    WebGLVideo.prototype.rewind = function () {
        if (this.playing) {
            this.video.currentTime = 0;

            return true;
        }

        return false;
    };

    WebGLVideo.prototype.destroy = function () {
        this.stop();

        if (this.video) {
            if (this.elementAdded) {
                this.elementAdded = false;
                TurbulenzEngine.canvas.parentElement.removeChild(this.video);
            }
            this.video = null;
        }
    };

    WebGLVideo.create = function (params) {
        var v = new WebGLVideo();

        var onload = params.onload;
        var looping = params.looping;
        var src = params.src;

        var userAgent = navigator.userAgent.toLowerCase();

        var video = (document.createElement('video'));
        video.preload = 'auto';
        video.autobuffer = true;
        video.muted = true;
        if (looping) {
            if (video.loop !== undefined && !userAgent.match(/firefox/)) {
                video.loop = true;
            } else {
                video.onended = function () {
                    video.src = src;
                    video.play();
                };
            }
        } else {
            video.onended = function () {
                v.playing = false;
            };
        }

        v.video = video;
        v.src = src;
        v.playing = false;
        v.paused = false;

        if (userAgent.match(/safari/) && !userAgent.match(/chrome/)) {
            //video.setAttribute("style", "display: none;");
            video.setAttribute("style", "visibility: hidden;");
            TurbulenzEngine.canvas.parentElement.appendChild(video);
            v.elementAdded = true;
        }

        if (video.webkitDecodedFrameCount !== undefined) {
            var lastFrameCount = -1, tell = 0;
            Object.defineProperty(v, "tell", {
                get: function tellFn() {
                    if (lastFrameCount !== this.video.webkitDecodedFrameCount) {
                        lastFrameCount = this.video.webkitDecodedFrameCount;
                        tell = this.video.currentTime;
                    }
                    return tell;
                },
                enumerable: true,
                configurable: false
            });
        } else {
            Object.defineProperty(v, "tell", {
                get: function tellFn() {
                    return this.video.currentTime;
                },
                enumerable: true,
                configurable: false
            });
        }

        Object.defineProperty(v, "looping", {
            get: function loopingFn() {
                return looping;
            },
            enumerable: true,
            configurable: false
        });

        var loadingVideoFailed = function loadingVideoFailedFn(/* e */ ) {
            if (onload) {
                onload(null);
                onload = null;
            }
            video.removeEventListener("error", loadingVideoFailed);
            video = null;
            v.video = null;
            v.playing = false;
        };
        video.addEventListener("error", loadingVideoFailed, false);

        var videoCanPlay = function videoCanPlayFn() {
            v.length = video.duration;
            v.width = video.videoWidth;
            v.height = video.videoHeight;

            if (onload) {
                onload(v, 200);
                onload = null;
            }

            video.removeEventListener("progress", checkProgress);
            video.removeEventListener("canplaythrough", videoCanPlay);
        };
        var checkProgress = function checkProgressFn() {
            if (0 < video.buffered.length && video.buffered.end(0) >= video.duration) {
                videoCanPlay();
            }
        };
        video.addEventListener("progress", checkProgress, false);
        video.addEventListener("canplaythrough", videoCanPlay, false);

        video.crossorigin = 'anonymous';
        video.src = src;

        return v;
    };
    WebGLVideo.version = 1;
    return WebGLVideo;
})();

//
// WebGLRenderBuffer
//
var WebGLRenderBuffer = (function () {
    function WebGLRenderBuffer() {
    }
    WebGLRenderBuffer.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var glBuffer = this.glBuffer;
            if (glBuffer) {
                var gl = gd.gl;
                if (gl) {
                    gl.deleteRenderbuffer(glBuffer);
                }
                delete this.glBuffer;
            }

            delete this.gd;
        }
    };

    WebGLRenderBuffer.create = function (gd, params) {
        var renderBuffer = new WebGLRenderBuffer();

        var width = params.width;
        var height = params.height;
        var format = params.format;
        if (typeof format === 'string') {
            format = gd['PIXELFORMAT_' + format];
        }

        if (format !== gd.PIXELFORMAT_D24S8 && format !== gd.PIXELFORMAT_D16) {
            return null;
        }

        var gl = gd.gl;

        var glBuffer = gl.createRenderbuffer();

        gl.bindRenderbuffer(gl.RENDERBUFFER, glBuffer);

        var internalFormat;
        var attachment;
        if (gd.PIXELFORMAT_D16 === format) {
            internalFormat = gl.DEPTH_COMPONENT16;
            attachment = gl.DEPTH_ATTACHMENT;
        } else {
            internalFormat = gl.DEPTH_STENCIL;
            attachment = gl.DEPTH_STENCIL_ATTACHMENT;
        }

        // else if (gd.PIXELFORMAT_S8 === format)
        // {
        //     internalFormat = gl.STENCIL_INDEX8;
        //     attachment = gl.STENCIL_ATTACHMENT;
        // }
        gl.renderbufferStorage(gl.RENDERBUFFER, internalFormat, width, height);
        renderBuffer.width = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_WIDTH);
        renderBuffer.height = gl.getRenderbufferParameter(gl.RENDERBUFFER, gl.RENDERBUFFER_HEIGHT);

        gl.bindRenderbuffer(gl.RENDERBUFFER, null);

        if (renderBuffer.width < width || renderBuffer.height < height) {
            gl.deleteRenderbuffer(glBuffer);
            return null;
        }

        renderBuffer.gd = gd;
        renderBuffer.format = format;
        renderBuffer.glDepthAttachment = attachment;
        renderBuffer.glBuffer = glBuffer;
        renderBuffer.id = ++gd.counters.renderBuffers;

        return renderBuffer;
    };
    WebGLRenderBuffer.version = 1;
    return WebGLRenderBuffer;
})();

//
// WebGLRenderTarget
//
var WebGLRenderTarget = (function () {
    function WebGLRenderTarget() {
    }
    WebGLRenderTarget.prototype.copyBox = function (dst, src) {
        dst[0] = src[0];
        dst[1] = src[1];
        dst[2] = src[2];
        dst[3] = src[3];
    };

    WebGLRenderTarget.prototype.bind = function () {
        var gd = this.gd;
        var gl = gd.gl;

        if (this.colorTexture0) {
            gd.unbindTexture(this.colorTexture0.glTexture);
            if (this.colorTexture1) {
                gd.unbindTexture(this.colorTexture1.glTexture);
                if (this.colorTexture2) {
                    gd.unbindTexture(this.colorTexture2.glTexture);
                    if (this.colorTexture3) {
                        gd.unbindTexture(this.colorTexture3.glTexture);
                    }
                }
            }
        }
        if (this.depthTexture) {
            gd.unbindTexture(this.depthTexture.glTexture);
        }

        gl.bindFramebuffer(gl.FRAMEBUFFER, this.glObject);

        var drawBuffersExtension = gd.drawBuffersExtension;
        if (drawBuffersExtension) {
            if (gd.WEBGL_draw_buffers) {
                drawBuffersExtension.drawBuffersWEBGL(this.buffers);
            } else {
                drawBuffersExtension.drawBuffersEXT(this.buffers);
            }
        }

        var state = gd.state;
        this.copyBox(this.oldViewportBox, state.viewportBox);
        this.copyBox(this.oldScissorBox, state.scissorBox);
        gd.setViewport(0, 0, this.width, this.height);
        gd.setScissor(0, 0, this.width, this.height);

        return true;
    };

    WebGLRenderTarget.prototype.unbind = function () {
        var gd = this.gd;
        var gl = gd.gl;

        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        var drawBuffersExtension = gd.drawBuffersExtension;
        if (drawBuffersExtension) {
            var buffers = [gl.BACK];

            if (gd.WEBGL_draw_buffers) {
                drawBuffersExtension.drawBuffersWEBGL(buffers);
            } else {
                drawBuffersExtension.drawBuffersEXT(buffers);
            }
        }

        var box = this.oldViewportBox;
        gd.setViewport(box[0], box[1], box[2], box[3]);
        box = this.oldScissorBox;
        gd.setScissor(box[0], box[1], box[2], box[3]);

        if (this.colorTexture0) {
            this.colorTexture0.updateMipmaps(this.face);
            if (this.colorTexture1) {
                this.colorTexture1.updateMipmaps(this.face);
                if (this.colorTexture2) {
                    this.colorTexture2.updateMipmaps(this.face);
                    if (this.colorTexture3) {
                        this.colorTexture3.updateMipmaps(this.face);
                    }
                }
            }
        }
        if (this.depthTexture) {
            this.depthTexture.updateMipmaps(this.face);
        }
    };

    WebGLRenderTarget.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var glObject = this.glObject;
            if (glObject) {
                var gl = gd.gl;
                if (gl) {
                    gl.deleteFramebuffer(glObject);
                }
                delete this.glObject;
            }

            delete this.colorTexture0;
            delete this.colorTexture1;
            delete this.colorTexture2;
            delete this.colorTexture3;
            delete this.depthBuffer;
            delete this.depthTexture;
            delete this.gd;
        }
    };

    WebGLRenderTarget.create = function (gd, params) {
        var renderTarget = new WebGLRenderTarget();

        var colorTexture0 = (params.colorTexture0);
        var colorTexture1 = (colorTexture0 ? (params.colorTexture1 || null) : null);
        var colorTexture2 = (colorTexture1 ? (params.colorTexture2 || null) : null);
        var colorTexture3 = (colorTexture2 ? (params.colorTexture3 || null) : null);
        var depthBuffer = (params.depthBuffer || null);
        var depthTexture = (params.depthTexture || null);
        var face = params.face;

        var maxSupported = gd.maxSupported("RENDERTARGET_COLOR_TEXTURES");
        if (colorTexture1 && maxSupported < 2) {
            return null;
        }
        if (colorTexture2 && maxSupported < 3) {
            return null;
        }
        if (colorTexture3 && maxSupported < 4) {
            return null;
        }

        var gl = gd.gl;
        var colorAttachment0 = gl.COLOR_ATTACHMENT0;

        var glObject = gl.createFramebuffer();

        gl.bindFramebuffer(gl.FRAMEBUFFER, glObject);

        var width, height;
        if (colorTexture0) {
            width = colorTexture0.width;
            height = colorTexture0.height;

            var glTexture = colorTexture0.glTexture;
            if (glTexture === undefined) {
                (TurbulenzEngine).callOnError("Color texture is not a Texture");
                gl.bindFramebuffer(gl.FRAMEBUFFER, null);
                gl.deleteFramebuffer(glObject);
                return null;
            }

            if (colorTexture0.cubemap) {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
            } else {
                gl.framebufferTexture2D(gl.FRAMEBUFFER, colorAttachment0, gl.TEXTURE_2D, glTexture, 0);
            }

            if (colorTexture1) {
                glTexture = colorTexture1.glTexture;
                if (colorTexture1.cubemap) {
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
                } else {
                    gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 1), gl.TEXTURE_2D, glTexture, 0);
                }

                if (colorTexture2) {
                    glTexture = colorTexture2.glTexture;
                    if (colorTexture1.cubemap) {
                        gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
                    } else {
                        gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 2), gl.TEXTURE_2D, glTexture, 0);
                    }

                    if (colorTexture3) {
                        glTexture = colorTexture3.glTexture;
                        if (colorTexture1.cubemap) {
                            gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), (gl.TEXTURE_CUBE_MAP_POSITIVE_X + face), glTexture, 0);
                        } else {
                            gl.framebufferTexture2D(gl.FRAMEBUFFER, (colorAttachment0 + 3), gl.TEXTURE_2D, glTexture, 0);
                        }
                    }
                }
            }
        } else if (depthTexture) {
            width = depthTexture.width;
            height = depthTexture.height;
        } else if (depthBuffer) {
            width = depthBuffer.width;
            height = depthBuffer.height;
        } else {
            (TurbulenzEngine).callOnError("No RenderBuffers or Textures specified for this RenderTarget");
            gl.bindFramebuffer(gl.FRAMEBUFFER, null);
            gl.deleteFramebuffer(glObject);
            return null;
        }

        if (depthTexture) {
            gl.framebufferTexture2D(gl.FRAMEBUFFER, depthTexture.glDepthAttachment, gl.TEXTURE_2D, depthTexture.glTexture, 0);
        } else if (depthBuffer) {
            gl.framebufferRenderbuffer(gl.FRAMEBUFFER, depthBuffer.glDepthAttachment, gl.RENDERBUFFER, depthBuffer.glBuffer);
        }

        var status = gl.checkFramebufferStatus(gl.FRAMEBUFFER);

        gl.bindFramebuffer(gl.FRAMEBUFFER, null);

        if (status !== gl.FRAMEBUFFER_COMPLETE) {
            gl.deleteFramebuffer(glObject);
            return null;
        }

        renderTarget.gd = gd;
        renderTarget.glObject = glObject;
        renderTarget.colorTexture0 = colorTexture0;
        renderTarget.colorTexture1 = colorTexture1;
        renderTarget.colorTexture2 = colorTexture2;
        renderTarget.colorTexture3 = colorTexture3;
        renderTarget.depthBuffer = depthBuffer;
        renderTarget.depthTexture = depthTexture;
        renderTarget.width = width;
        renderTarget.height = height;
        renderTarget.face = face;

        if (gd.drawBuffersExtension) {
            var buffers;
            if (colorTexture0) {
                buffers = [colorAttachment0];
                if (colorTexture1) {
                    buffers.push(colorAttachment0 + 1);
                    if (colorTexture2) {
                        buffers.push(colorAttachment0 + 2);
                        if (colorTexture3) {
                            buffers.push(colorAttachment0 + 3);
                        }
                    }
                }
            } else {
                buffers = [gl.NONE];
            }
            renderTarget.buffers = buffers;
        }

        renderTarget.id = ++gd.counters.renderTargets;

        return renderTarget;
    };
    WebGLRenderTarget.version = 1;
    return WebGLRenderTarget;
})();

WebGLRenderTarget.prototype.oldViewportBox = [];
WebGLRenderTarget.prototype.oldScissorBox = [];

;

var WebGLIndexBuffer = (function () {
    function WebGLIndexBuffer() {
    }
    WebGLIndexBuffer.prototype.map = function (offset, numIndices) {
        if (offset === undefined) {
            offset = 0;
        }
        if (numIndices === undefined) {
            numIndices = this.numIndices;
        }

        var gd = this.gd;
        var gl = gd.gl;

        var format = this.format;
        var data;
        if (format === gl.UNSIGNED_BYTE) {
            data = new Uint8Array(numIndices);
        } else if (format === gl.UNSIGNED_SHORT) {
            data = new Uint16Array(numIndices);
        } else {
            data = new Uint32Array(numIndices);
        }

        var numValues = 0;
        var writer = function indexBufferWriterFn() {
            var numArguments = arguments.length;
            for (var n = 0; n < numArguments; n += 1) {
                data[numValues] = arguments[n];
                numValues += 1;
            }
        };
        writer.write = writer;
        writer.data = data;
        writer.offset = offset;
        writer.getNumWrittenIndices = function getNumWrittenIndicesFn() {
            return numValues;
        };
        writer.write = writer;
        return writer;
    };

    WebGLIndexBuffer.prototype.unmap = function (writer) {
        if (writer) {
            var gd = this.gd;
            var gl = gd.gl;

            var data = writer.data;
            delete writer.data;

            var offset = writer.offset;

            delete writer.write;

            var numIndices = writer.getNumWrittenIndices();
            if (!numIndices) {
                return;
            }

            if (numIndices < data.length) {
                data = data.subarray(0, numIndices);
            }

            gd.setIndexBuffer(this);

            if (numIndices < this.numIndices) {
                gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, data);
            } else {
                gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, data, this.usage);
            }
        }
    };

    WebGLIndexBuffer.prototype.setData = function (data, offset, numIndices) {
        if (offset === undefined) {
            offset = 0;
        }
        if (numIndices === undefined) {
            numIndices = this.numIndices;
        }
        /* debug.assert(offset + numIndices <= this.numIndices, "IndexBuffer.setData: invalid 'offset' and/or " + "'numIndices' arguments"); */

        var gd = this.gd;
        var gl = gd.gl;

        var bufferData;
        var format = this.format;
        if (format === gl.UNSIGNED_BYTE) {
            if (data instanceof Uint8Array) {
                bufferData = data;
            } else {
                bufferData = new Uint8Array(data);
            }
        } else if (format === gl.UNSIGNED_SHORT) {
            if (data instanceof Uint16Array) {
                bufferData = data;
            } else {
                bufferData = new Uint16Array(data);
            }
            offset *= 2;
        } else if (format === gl.UNSIGNED_INT) {
            if (data instanceof Uint32Array) {
                bufferData = data;
            } else {
                bufferData = new Uint32Array(data);
            }
            offset *= 4;
        }
        data = undefined;

        if (numIndices < bufferData.length) {
            bufferData = bufferData.subarray(0, numIndices);
        }

        gd.setIndexBuffer(this);

        if (numIndices < this.numIndices) {
            gl.bufferSubData(gl.ELEMENT_ARRAY_BUFFER, offset, bufferData);
        } else {
            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, bufferData, this.usage);
        }
    };

    WebGLIndexBuffer.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var glBuffer = this.glBuffer;
            if (glBuffer) {
                var gl = gd.gl;
                if (gl) {
                    gd.unsetIndexBuffer(this);
                    gl.deleteBuffer(glBuffer);
                }
                delete this.glBuffer;
            }

            delete this.gd;
        }
    };

    WebGLIndexBuffer.create = function (gd, params) {
        var gl = gd.gl;

        var ib = new WebGLIndexBuffer();
        ib.gd = gd;

        var numIndices = params.numIndices;
        ib.numIndices = numIndices;

        var format = params.format;
        if (typeof format === "string") {
            format = gd['INDEXFORMAT_' + format];
        }
        ib.format = format;

        var stride;
        if (format === gl.UNSIGNED_BYTE) {
            stride = 1;
        } else if (format === gl.UNSIGNED_SHORT) {
            stride = 2;
        } else {
            stride = 4;
        }
        ib.stride = stride;

        // Avoid dot notation lookup to prevent Google Closure complaining about transient being a keyword
        ib['transient'] = (params['transient'] || false);
        ib.dynamic = (params.dynamic || ib['transient']);
        ib.usage = (ib['transient'] ? gl.STREAM_DRAW : (ib.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW));

        ib.glBuffer = gl.createBuffer();

        if (params.data) {
            ib.setData(params.data, 0, numIndices);
        } else {
            gd.setIndexBuffer(ib);

            gl.bufferData(gl.ELEMENT_ARRAY_BUFFER, (numIndices * stride), ib.usage);
        }

        ib.id = ++gd.counters.indexBuffers;

        return ib;
    };
    WebGLIndexBuffer.version = 1;
    return WebGLIndexBuffer;
})();

//
// WebGLSemantics
//
var WebGLSemantics = (function () {
    function WebGLSemantics() {
    }
    WebGLSemantics.create = function (gd, attributes) {
        var semantics = new WebGLSemantics();

        var numAttributes = attributes.length;
        semantics.length = numAttributes;
        for (var i = 0; i < numAttributes; i += 1) {
            var attribute = attributes[i];
            if (typeof attribute === "string") {
                semantics[i] = gd['SEMANTIC_' + attribute];
            } else {
                semantics[i] = attribute;
            }
        }

        return semantics;
    };
    WebGLSemantics.version = 1;
    return WebGLSemantics;
})();

//
// WebGLVertexBuffer
//
var WebGLVertexBuffer = (function () {
    function WebGLVertexBuffer() {
    }
    WebGLVertexBuffer.prototype.map = function (offset, numVertices) {
        if (offset === undefined) {
            offset = 0;
        }
        if (numVertices === undefined) {
            numVertices = this.numVertices;
        }

        var gd = this.gd;
        var gl = gd.gl;

        var numValuesPerVertex = this.stride;
        var attributes = this.attributes;
        var numAttributes = attributes.length;

        var data, writer;
        var numValues = 0;

        if (this.hasSingleFormat) {
            var maxNumValues = (numVertices * numValuesPerVertex);
            var format = attributes[0].format;

            if (format === gl.FLOAT) {
                data = new Float32Array(maxNumValues);
            } else if (format === gl.BYTE) {
                data = new Int8Array(maxNumValues);
            } else if (format === gl.UNSIGNED_BYTE) {
                data = new Uint8Array(maxNumValues);
            } else if (format === gl.SHORT) {
                data = new Int16Array(maxNumValues);
            } else if (format === gl.UNSIGNED_SHORT) {
                data = new Uint16Array(maxNumValues);
            } else if (format === gl.INT) {
                data = new Int32Array(maxNumValues);
            } else if (format === gl.UNSIGNED_INT) {
                data = new Uint32Array(maxNumValues);
            }

            writer = function vertexBufferWriterSingleFn() {
                var numArguments = arguments.length;
                var currentArgument = 0;
                for (var a = 0; a < numAttributes; a += 1) {
                    var attribute = attributes[a];
                    var numComponents = attribute.numComponents;
                    var currentComponent = 0, j;
                    do {
                        if (currentArgument < numArguments) {
                            var value = arguments[currentArgument];
                            currentArgument += 1;
                            if (typeof value === "number") {
                                if (attribute.normalized) {
                                    value *= attribute.normalizationScale;
                                }
                                data[numValues] = value;
                                numValues += 1;
                                currentComponent += 1;
                            } else if (currentComponent === 0) {
                                var numSubArguments = value.length;
                                if (numSubArguments > numComponents) {
                                    numSubArguments = numComponents;
                                }
                                if (attribute.normalized) {
                                    var scale = attribute.normalizationScale;
                                    for (j = 0; j < numSubArguments; j += 1) {
                                        data[numValues] = (value[j] * scale);
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                } else {
                                    for (j = 0; j < numSubArguments; j += 1) {
                                        data[numValues] = value[j];
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                }
                                while (currentComponent < numComponents) {
                                    // No need to clear to zeros
                                    numValues += 1;
                                    currentComponent += 1;
                                }
                                break;
                            } else {
                                (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                return null;
                            }
                        } else {
                            // No need to clear to zeros
                            numValues += 1;
                            currentComponent += 1;
                        }
                    } while(currentComponent < numComponents);
                }
            };
        } else {
            var destOffset = 0;
            var bufferSize = (numVertices * this.strideInBytes);

            data = new ArrayBuffer(bufferSize);

            if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
                var dataView = new DataView(data);

                writer = function vertexBufferWriterDataViewFn() {
                    var numArguments = arguments.length;
                    var currentArgument = 0;
                    for (var a = 0; a < numAttributes; a += 1) {
                        var attribute = attributes[a];
                        var numComponents = attribute.numComponents;
                        var setter = attribute.typedSetter;
                        var componentStride = attribute.componentStride;
                        var currentComponent = 0, j;
                        do {
                            if (currentArgument < numArguments) {
                                var value = arguments[currentArgument];
                                currentArgument += 1;
                                if (typeof value === "number") {
                                    if (attribute.normalized) {
                                        value *= attribute.normalizationScale;
                                    }
                                    setter.call(dataView, destOffset, value, true);
                                    destOffset += componentStride;
                                    currentComponent += 1;
                                    numValues += 1;
                                } else if (currentComponent === 0) {
                                    var numSubArguments = value.length;
                                    if (numSubArguments > numComponents) {
                                        numSubArguments = numComponents;
                                    }
                                    if (attribute.normalized) {
                                        var scale = attribute.normalizationScale;
                                        for (j = 0; j < numSubArguments; j += 1) {
                                            setter.call(dataView, destOffset, (value[j] * scale), true);
                                            destOffset += componentStride;
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    } else {
                                        for (j = 0; j < numSubArguments; j += 1) {
                                            setter.call(dataView, destOffset, value[j], true);
                                            destOffset += componentStride;
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    }
                                    while (currentComponent < numComponents) {
                                        // No need to clear to zeros
                                        numValues += 1;
                                        currentComponent += 1;
                                    }
                                    break;
                                } else {
                                    (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                    return null;
                                }
                            } else {
                                // No need to clear to zeros
                                numValues += 1;
                                currentComponent += 1;
                            }
                        } while(currentComponent < numComponents);
                    }
                };
            } else {
                writer = function vertexBufferWriterMultiFn() {
                    var numArguments = arguments.length;
                    var currentArgument = 0;
                    var dest;
                    for (var a = 0; a < numAttributes; a += 1) {
                        var attribute = attributes[a];
                        var numComponents = attribute.numComponents;
                        dest = new attribute.typedArray(data, destOffset, numComponents);
                        destOffset += attribute.stride;

                        var currentComponent = 0, j;
                        do {
                            if (currentArgument < numArguments) {
                                var value = arguments[currentArgument];
                                currentArgument += 1;
                                if (typeof value === "number") {
                                    if (attribute.normalized) {
                                        value *= attribute.normalizationScale;
                                    }
                                    dest[currentComponent] = value;
                                    currentComponent += 1;
                                    numValues += 1;
                                } else if (currentComponent === 0) {
                                    var numSubArguments = value.length;
                                    if (numSubArguments > numComponents) {
                                        numSubArguments = numComponents;
                                    }
                                    if (attribute.normalized) {
                                        var scale = attribute.normalizationScale;
                                        for (j = 0; j < numSubArguments; j += 1) {
                                            dest[currentComponent] = (value[j] * scale);
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    } else {
                                        for (j = 0; j < numSubArguments; j += 1) {
                                            dest[currentComponent] = value[j];
                                            currentComponent += 1;
                                            numValues += 1;
                                        }
                                    }
                                    while (currentComponent < numComponents) {
                                        // No need to clear to zeros
                                        currentComponent += 1;
                                        numValues += 1;
                                    }
                                    break;
                                } else {
                                    (TurbulenzEngine).callOnError('Missing values for attribute ' + a);
                                    return null;
                                }
                            } else {
                                // No need to clear to zeros
                                currentComponent += 1;
                                numValues += 1;
                            }
                        } while(currentComponent < numComponents);
                    }
                };
            }
        }

        writer.data = data;
        writer.offset = offset;
        writer.getNumWrittenVertices = function getNumWrittenVerticesFn() {
            return Math.floor(numValues / numValuesPerVertex);
        };
        writer.getNumWrittenValues = function getNumWrittenValuesFn() {
            return numValues;
        };
        writer.write = writer;
        return writer;
    };

    WebGLVertexBuffer.prototype.unmap = function (writer) {
        if (writer) {
            var data = writer.data;
            delete writer.data;

            delete writer.write;

            var numVertices = writer.getNumWrittenVertices();
            if (!numVertices) {
                return;
            }

            var offset = writer.offset;

            var stride = this.strideInBytes;

            if (this.hasSingleFormat) {
                var numValues = writer.getNumWrittenValues();
                if (numValues < data.length) {
                    data = data.subarray(0, numValues);
                }
            } else {
                var numBytes = (numVertices * stride);
                if (numBytes < data.byteLength) {
                    data = data.slice(0, numBytes);
                }
            }

            var gd = this.gd;
            var gl = gd.gl;

            gd.bindVertexBuffer(this.glBuffer);

            if (numVertices < this.numVertices) {
                gl.bufferSubData(gl.ARRAY_BUFFER, (offset * stride), data);
            } else {
                gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
            }
        }
    };

    WebGLVertexBuffer.prototype.setData = function (data, offset, numVertices) {
        if (offset === undefined) {
            offset = 0;
        }
        if (numVertices === undefined) {
            numVertices = this.numVertices;
        }

        var gd = this.gd;
        var gl = gd.gl;
        var strideInBytes = this.strideInBytes;

        if (data.constructor === ArrayBuffer) {
            gd.bindVertexBuffer(this.glBuffer);

            if (numVertices < this.numVertices) {
                gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), data);
            } else {
                gl.bufferData(gl.ARRAY_BUFFER, data, this.usage);
            }
            return;
        }

        var attributes = this.attributes;
        var numAttributes = this.numAttributes;
        var attribute, format, bufferData, TypedArrayConstructor;

        if (this.hasSingleFormat) {
            attribute = attributes[0];
            format = attribute.format;

            if (format === gl.FLOAT) {
                if (!(data instanceof Float32Array)) {
                    TypedArrayConstructor = Float32Array;
                }
            } else if (format === gl.BYTE) {
                if (!(data instanceof Int8Array)) {
                    TypedArrayConstructor = Int8Array;
                }
            } else if (format === gl.UNSIGNED_BYTE) {
                if (!(data instanceof Uint8Array)) {
                    TypedArrayConstructor = Uint8Array;
                }
            } else if (format === gl.SHORT) {
                if (!(data instanceof Int16Array)) {
                    TypedArrayConstructor = Int16Array;
                }
            } else if (format === gl.UNSIGNED_SHORT) {
                if (!(data instanceof Uint16Array)) {
                    TypedArrayConstructor = Uint16Array;
                }
            } else if (format === gl.INT) {
                if (!(data instanceof Int32Array)) {
                    TypedArrayConstructor = Int32Array;
                }
            } else if (format === gl.UNSIGNED_INT) {
                if (!(data instanceof Uint32Array)) {
                    TypedArrayConstructor = Uint32Array;
                }
            }

            var numValuesPerVertex = this.stride;
            var numValues = (numVertices * numValuesPerVertex);

            if (TypedArrayConstructor) {
                if (attribute.normalized) {
                    data = this.scaleValues(data, attribute.normalizationScale, numValues);
                }
                bufferData = new TypedArrayConstructor(data);
                if (numValues < bufferData.length) {
                    bufferData = bufferData.subarray(0, numValues);
                }
            } else {
                bufferData = data;
            }

            if (numValues < data.length) {
                bufferData = bufferData.subarray(0, numValues);
            }
        } else {
            var bufferSize = (numVertices * strideInBytes);

            bufferData = new ArrayBuffer(bufferSize);

            var srcOffset = 0, destOffset = 0, v, c, a, numComponents, componentStride, scale;
            if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
                var dataView = new DataView(bufferData);

                for (v = 0; v < numVertices; v += 1) {
                    for (a = 0; a < numAttributes; a += 1) {
                        attribute = attributes[a];
                        numComponents = attribute.numComponents;
                        componentStride = attribute.componentStride;
                        var setter = attribute.typedSetter;
                        if (attribute.normalized) {
                            scale = attribute.normalizationScale;
                            for (c = 0; c < numComponents; c += 1) {
                                setter.call(dataView, destOffset, (data[srcOffset] * scale), true);
                                destOffset += componentStride;
                                srcOffset += 1;
                            }
                        } else {
                            for (c = 0; c < numComponents; c += 1) {
                                setter.call(dataView, destOffset, data[srcOffset], true);
                                destOffset += componentStride;
                                srcOffset += 1;
                            }
                        }
                    }
                }
            } else {
                for (v = 0; v < numVertices; v += 1) {
                    for (a = 0; a < numAttributes; a += 1) {
                        attribute = attributes[a];
                        numComponents = attribute.numComponents;
                        var dest = new attribute.typedArray(bufferData, destOffset, numComponents);
                        destOffset += attribute.stride;
                        if (attribute.normalized) {
                            scale = attribute.normalizationScale;
                            for (c = 0; c < numComponents; c += 1) {
                                dest[c] = (data[srcOffset] * scale);
                                srcOffset += 1;
                            }
                        } else {
                            for (c = 0; c < numComponents; c += 1) {
                                dest[c] = data[srcOffset];
                                srcOffset += 1;
                            }
                        }
                    }
                }
            }
        }
        data = undefined;

        gd.bindVertexBuffer(this.glBuffer);

        if (numVertices < this.numVertices) {
            gl.bufferSubData(gl.ARRAY_BUFFER, (offset * strideInBytes), bufferData);
        } else {
            gl.bufferData(gl.ARRAY_BUFFER, bufferData, this.usage);
        }
    };

    // Internal
    WebGLVertexBuffer.prototype.scaleValues = function (values, scale, numValues) {
        if (numValues === undefined) {
            numValues = values.length;
        }
        var scaledValues = new values.constructor(numValues);
        for (var n = 0; n < numValues; n += 1) {
            scaledValues[n] = (values[n] * scale);
        }
        return scaledValues;
    };

    WebGLVertexBuffer.prototype.bindAttributes = function (numAttributes, attributes, offset) {
        var gd = this.gd;
        var gl = gd.gl;
        var vertexAttributes = this.attributes;
        var stride = this.strideInBytes;
        var attributeMask = 0;
        for (var n = 0; n < numAttributes; n += 1) {
            var vertexAttribute = vertexAttributes[n];
            var attribute = attributes[n];

            attributeMask |= (1 << attribute);

            gl.vertexAttribPointer(attribute, vertexAttribute.numComponents, vertexAttribute.format, vertexAttribute.normalized, stride, offset);

            offset += vertexAttribute.stride;
        }
        return attributeMask;
    };

    WebGLVertexBuffer.prototype.setAttributes = function (attributes) {
        var gd = this.gd;

        var numAttributes = attributes.length;
        this.numAttributes = numAttributes;

        this.attributes = [];
        var stride = 0, numValuesPerVertex = 0, hasSingleFormat = true;

        for (var i = 0; i < numAttributes; i += 1) {
            var format = attributes[i];
            if (typeof format === "string") {
                format = gd['VERTEXFORMAT_' + format];
            }
            this.attributes[i] = format;
            stride += format.stride;
            numValuesPerVertex += format.numComponents;

            if (hasSingleFormat && i) {
                if (format.format !== this.attributes[i - 1].format) {
                    hasSingleFormat = false;
                }
            }
        }
        this.strideInBytes = stride;
        this.stride = numValuesPerVertex;
        this.hasSingleFormat = hasSingleFormat;

        return stride;
    };

    WebGLVertexBuffer.prototype.resize = function (size) {
        if (size !== (this.strideInBytes * this.numVertices)) {
            var gd = this.gd;
            var gl = gd.gl;

            gd.bindVertexBuffer(this.glBuffer);

            var bufferType = gl.ARRAY_BUFFER;
            gl.bufferData(bufferType, size, this.usage);

            var bufferSize = gl.getBufferParameter(bufferType, gl.BUFFER_SIZE);
            this.numVertices = Math.floor(bufferSize / this.strideInBytes);
        }
    };

    WebGLVertexBuffer.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var glBuffer = this.glBuffer;
            if (glBuffer) {
                var gl = gd.gl;
                if (gl) {
                    gd.unbindVertexBuffer(glBuffer);
                    gl.deleteBuffer(glBuffer);
                }
                delete this.glBuffer;
            }

            delete this.gd;
        }
    };

    WebGLVertexBuffer.create = function (gd, params) {
        var gl = gd.gl;

        var vb = new WebGLVertexBuffer();
        vb.gd = gd;

        var numVertices = params.numVertices;
        vb.numVertices = numVertices;

        var strideInBytes = vb.setAttributes(params.attributes);

        // Avoid dot notation lookup to prevent Google Closure complaining
        // about transient being a keyword
        vb['transient'] = (params['transient'] || false);
        vb.dynamic = (params.dynamic || vb['transient']);
        vb.usage = (vb['transient'] ? gl.STREAM_DRAW : (vb.dynamic ? gl.DYNAMIC_DRAW : gl.STATIC_DRAW));
        vb.glBuffer = gl.createBuffer();

        var bufferSize = (numVertices * strideInBytes);

        if (params.data) {
            vb.setData(params.data, 0, numVertices);
        } else {
            gd.bindVertexBuffer(vb.glBuffer);

            gl.bufferData(gl.ARRAY_BUFFER, bufferSize, vb.usage);
        }

        vb.id = ++gd.counters.vertexBuffers;

        return vb;
    };
    WebGLVertexBuffer.version = 1;
    return WebGLVertexBuffer;
})();

;

;

;

;

var WebGLPass = (function () {
    function WebGLPass() {
    }
    WebGLPass.prototype.updateParametersData = function (gd) {
        var gl = gd.gl;

        this.dirty = false;

        // Set parameters
        var parameters = this.parameters;
        for (var p in parameters) {
            if (parameters.hasOwnProperty(p)) {
                var parameter = parameters[p];
                if (parameter.dirty) {
                    parameter.dirty = 0;

                    var paramInfo = parameter.info;
                    var location = parameter.location;
                    if (paramInfo && null !== location) {
                        var parameterValues = paramInfo.values;

                        var numColumns;
                        if (paramInfo.type === 'float') {
                            numColumns = paramInfo.columns;
                            if (4 === numColumns) {
                                gl.uniform4fv(location, parameterValues);
                            } else if (3 === numColumns) {
                                gl.uniform3fv(location, parameterValues);
                            } else if (2 === numColumns) {
                                gl.uniform2fv(location, parameterValues);
                            } else if (1 === paramInfo.rows) {
                                gl.uniform1f(location, parameterValues[0]);
                            } else {
                                gl.uniform1fv(location, parameterValues);
                            }
                        } else if (paramInfo.sampler !== undefined) {
                            gd.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                        } else {
                            numColumns = paramInfo.columns;
                            if (4 === numColumns) {
                                gl.uniform4iv(location, parameterValues);
                            } else if (3 === numColumns) {
                                gl.uniform3iv(location, parameterValues);
                            } else if (2 === numColumns) {
                                gl.uniform2iv(location, parameterValues);
                            } else if (1 === paramInfo.rows) {
                                gl.uniform1i(location, parameterValues[0]);
                            } else {
                                gl.uniform1iv(location, parameterValues);
                            }
                        }
                    }
                }
            }
        }
    };

    WebGLPass.prototype.initializeParameters = function (gd) {
        var gl = gd.gl;

        var glProgram = this.glProgram;

        gd.setProgram(glProgram);

        var passParameters = this.parameters;
        for (var p in passParameters) {
            if (passParameters.hasOwnProperty(p)) {
                var parameter = passParameters[p];

                var paramInfo = parameter.info;
                if (paramInfo) {
                    var location = gl.getUniformLocation(glProgram, p);
                    if (null !== location) {
                        parameter.location = location;

                        if (paramInfo.sampler) {
                            gl.uniform1i(location, parameter.textureUnit);
                        } else {
                            var parameterValues = paramInfo.values;

                            var numColumns;
                            if (paramInfo.type === 'float') {
                                numColumns = paramInfo.columns;
                                if (4 === numColumns) {
                                    gl.uniform4fv(location, parameterValues);
                                } else if (3 === numColumns) {
                                    gl.uniform3fv(location, parameterValues);
                                } else if (2 === numColumns) {
                                    gl.uniform2fv(location, parameterValues);
                                } else if (1 === paramInfo.rows) {
                                    gl.uniform1f(location, parameterValues[0]);
                                } else {
                                    gl.uniform1fv(location, parameterValues);
                                }
                            } else {
                                numColumns = paramInfo.columns;
                                if (4 === numColumns) {
                                    gl.uniform4iv(location, parameterValues);
                                } else if (3 === numColumns) {
                                    gl.uniform3iv(location, parameterValues);
                                } else if (2 === numColumns) {
                                    gl.uniform2iv(location, parameterValues);
                                } else if (1 === paramInfo.rows) {
                                    gl.uniform1i(location, parameterValues[0]);
                                } else {
                                    gl.uniform1iv(location, parameterValues);
                                }
                            }
                        }
                    }
                }
            }
        }
    };

    WebGLPass.prototype.destroy = function () {
        delete this.glProgram;
        delete this.semanticsMask;
        delete this.parameters;
        delete this.states;
        delete this.statesSet;
    };

    WebGLPass.create = function (gd, shader, params) {
        var gl = gd.gl;

        var pass = new WebGLPass();

        pass.name = (params.name || null);

        var programs = shader.programs;
        var parameters = shader.parameters;

        var parameterNames = params.parameters;
        var programNames = params.programs;
        var semanticNames = params.semantics;
        var states = params.states;

        var compoundProgramName = programNames.join(':');
        var linkedProgram = shader.linkedPrograms[compoundProgramName];
        var glProgram, semanticsMask, p, s;
        if (linkedProgram === undefined) {
            // Create GL program
            glProgram = gl.createProgram();

            var numPrograms = programNames.length;
            for (p = 0; p < numPrograms; p += 1) {
                var glShader = programs[programNames[p]];
                if (glShader) {
                    gl.attachShader(glProgram, glShader);
                }
            }

            var numSemantics = semanticNames.length;
            semanticsMask = 0;
            for (s = 0; s < numSemantics; s += 1) {
                var semanticName = semanticNames[s];
                var attribute = gd['SEMANTIC_' + semanticName];
                if (attribute !== undefined) {
                    semanticsMask |= (1 << attribute);
                    if (0 === semanticName.indexOf("ATTR")) {
                        gl.bindAttribLocation(glProgram, attribute, semanticName);
                    } else {
                        var attributeName = WebGLPass.semanticToAttr[semanticName];
                        gl.bindAttribLocation(glProgram, attribute, attributeName);
                    }
                }
            }

            gl.linkProgram(glProgram);

            shader.linkedPrograms[compoundProgramName] = {
                glProgram: glProgram,
                semanticsMask: semanticsMask
            };
        } else {
            //console.log('Reused program ' + compoundProgramName);
            glProgram = linkedProgram.glProgram;
            semanticsMask = linkedProgram.semanticsMask;
        }

        pass.glProgram = glProgram;
        pass.semanticsMask = semanticsMask;

        // Set parameters
        var numTextureUnits = 0;
        var passParameters = {};
        pass.parameters = passParameters;
        var numParameters = parameterNames ? parameterNames.length : 0;
        for (p = 0; p < numParameters; p += 1) {
            var parameterName = parameterNames[p];

            var parameter = {};
            passParameters[parameterName] = parameter;

            var paramInfo = parameters[parameterName];
            parameter.info = paramInfo;
            if (paramInfo) {
                parameter.location = null;
                if (paramInfo.sampler) {
                    parameter.textureUnit = numTextureUnits;
                    numTextureUnits += 1;
                } else {
                    parameter.textureUnit = undefined;
                }
            }
        }
        pass.numTextureUnits = numTextureUnits;
        pass.numParameters = numParameters;

        function equalRenderStates(defaultValues, values) {
            var numDefaultValues = defaultValues.length;
            var n;
            for (n = 0; n < numDefaultValues; n += 1) {
                if (defaultValues[n] !== values[n]) {
                    return false;
                }
            }
            return true;
        }

        var stateHandlers = gd.stateHandlers;
        var passStates = [];
        var passStatesSet = {};
        pass.states = passStates;
        pass.statesSet = passStatesSet;
        for (s in states) {
            if (states.hasOwnProperty(s)) {
                var stateHandler = stateHandlers[s];
                if (stateHandler) {
                    var values = stateHandler.parse(states[s]);
                    if (values !== null) {
                        if (equalRenderStates(stateHandler.defaultValues, values)) {
                            continue;
                        }
                        passStates.push({
                            name: s,
                            set: stateHandler.set,
                            reset: stateHandler.reset,
                            values: values
                        });
                        passStatesSet[s] = true;
                    } else {
                        (TurbulenzEngine).callOnError('Unknown value for state ' + s + ': ' + states[s]);
                    }
                }
            }
        }

        return pass;
    };
    WebGLPass.version = 1;

    WebGLPass.semanticToAttr = {
        POSITION: "ATTR0",
        POSITION0: "ATTR0",
        BLENDWEIGHT: "ATTR1",
        BLENDWEIGHT0: "ATTR1",
        NORMAL: "ATTR2",
        NORMAL0: "ATTR2",
        COLOR: "ATTR3",
        COLOR0: "ATTR3",
        COLOR1: "ATTR4",
        SPECULAR: "ATTR4",
        FOGCOORD: "ATTR5",
        TESSFACTOR: "ATTR5",
        PSIZE0: "ATTR6",
        BLENDINDICES: "ATTR7",
        BLENDINDICES0: "ATTR7",
        TEXCOORD: "ATTR8",
        TEXCOORD0: "ATTR8",
        TEXCOORD1: "ATTR9",
        TEXCOORD2: "ATTR10",
        TEXCOORD3: "ATTR11",
        TEXCOORD4: "ATTR12",
        TEXCOORD5: "ATTR13",
        TEXCOORD6: "ATTR14",
        TEXCOORD7: "ATTR15",
        TANGENT: "ATTR14",
        TANGENT0: "ATTR14",
        BINORMAL0: "ATTR15",
        BINORMAL: "ATTR15",
        PSIZE: "ATTR6"
    };
    return WebGLPass;
})();

//
// WebGLTechnique
//
var WebGLTechnique = (function () {
    function WebGLTechnique() {
    }
    WebGLTechnique.prototype.getPass = function (id) {
        var passes = this.passes;
        var numPasses = passes.length;
        if (typeof id === "string") {
            for (var n = 0; n < numPasses; n += 1) {
                var pass = passes[n];
                if (pass.name === id) {
                    return pass;
                }
            }
        } else {
            id = (id | 0);
            if (id < numPasses) {
                return passes[id];
            }
        }
        return null;
    };

    WebGLTechnique.prototype.activate = function (gd) {
        this.device = gd;

        if (!this.initialized) {
            this.shader.initialize(gd);
            this.initialize(gd);
        }

        /* if (debug) {
            gd.metrics.techniqueChanges += 1;
        } */
    };

    WebGLTechnique.prototype.deactivate = function () {
        this.device = null;
    };

    WebGLTechnique.prototype.checkProperties = function (gd) {
        // Check for parameters set directly into the technique...
        var fakeTechniqueParameters = {}, p;
        for (p in this) {
            if (p !== 'version' && p !== 'name' && p !== 'id' && p !== 'passes' && p !== 'numPasses' && p !== 'device' && p !== 'numParameters') {
                fakeTechniqueParameters[p] = this[p];
            }
        }

        if (fakeTechniqueParameters) {
            var passes = this.passes;
            if (passes.length === 1) {
                gd.setParametersImmediate(passes[0].parameters, fakeTechniqueParameters);
            } else {
                gd.setParametersDeferred(gd, passes, fakeTechniqueParameters);
            }

            for (p in fakeTechniqueParameters) {
                if (fakeTechniqueParameters.hasOwnProperty(p)) {
                    delete this[p];
                }
            }
        }
    };

    WebGLTechnique.prototype.initialize = function (gd) {
        if (this.initialized) {
            return;
        }

        var passes = this.passes;
        if (passes) {
            var numPasses = passes.length;
            var n;
            for (n = 0; n < numPasses; n += 1) {
                passes[n].initializeParameters(gd);
            }
        }

        if (Object.defineProperty) {
            this.initializeParametersSetters(gd);
        }

        this.initialized = true;
    };

    WebGLTechnique.prototype.initializeParametersSetters = function (gd) {
        var gl = gd.gl;

        function make_sampler_setter(pass, parameter) {
            return function (parameterValues) {
                if (this.device) {
                    gd.setTexture(parameter.textureUnit, parameterValues, parameter.info.sampler);
                } else {
                    pass.dirty = true;
                    parameter.dirty = 1;
                    parameter.info.values = parameterValues;
                }
            };
        }

        function make_float_uniform_setter(pass, parameter) {
            var paramInfo = parameter.info;
            var location = parameter.location;

            function setDeferredParameter(parameterValues) {
                if (typeof parameterValues !== 'number') {
                    var values = paramInfo.values;
                    var numValues = Math.min(paramInfo.numValues, parameterValues.length);
                    for (var v = 0; v < numValues; v += 1) {
                        values[v] = parameterValues[v];
                    }
                    parameter.dirty = Math.max(numValues, (parameter.dirty || 0));
                } else {
                    paramInfo.values[0] = parameterValues;
                    parameter.dirty = (parameter.dirty || 1);
                }
                pass.dirty = true;
            }

            switch (paramInfo.columns) {
                case 1:
                    if (1 === paramInfo.numValues) {
                        return function (parameterValues) {
                            if (this.device) {
                                gl.uniform1f(location, parameterValues);
                            } else {
                                setDeferredParameter(parameterValues);
                            }
                        };
                    }
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform1fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 2:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform2fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 3:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform3fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 4:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform4fv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                default:
                    return null;
            }
        }

        function make_int_uniform_setter(pass, parameter) {
            var paramInfo = parameter.info;
            var location = parameter.location;

            function setDeferredParameter(parameterValues) {
                if (typeof parameterValues !== 'number') {
                    var values = paramInfo.values;
                    var numValues = Math.min(paramInfo.numValues, parameterValues.length);
                    for (var v = 0; v < numValues; v += 1) {
                        values[v] = parameterValues[v];
                    }
                    parameter.dirty = Math.max(numValues, (parameter.dirty || 0));
                } else {
                    paramInfo.values[0] = parameterValues;
                    parameter.dirty = (parameter.dirty || 1);
                }
                pass.dirty = true;
            }

            switch (paramInfo.columns) {
                case 1:
                    if (1 === paramInfo.numValues) {
                        return function (parameterValues) {
                            if (this.device) {
                                gl.uniform1i(location, parameterValues);
                            } else {
                                setDeferredParameter(parameterValues);
                            }
                        };
                    }
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform1iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 2:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform2iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 3:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform3iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                case 4:
                    return function (parameterValues) {
                        if (this.device) {
                            gl.uniform4iv(location, parameterValues);
                        } else {
                            setDeferredParameter(parameterValues);
                        }
                    };
                default:
                    return null;
            }
        }

        var passes = this.passes;
        var numPasses = passes.length;
        var pass, parameters, p, parameter, paramInfo, setter;
        if (numPasses === 1) {
            pass = passes[0];
            parameters = pass.parameters;
            for (p in parameters) {
                if (parameters.hasOwnProperty(p)) {
                    parameter = parameters[p];
                    paramInfo = parameter.info;
                    if (paramInfo) {
                        if (undefined !== parameter.location) {
                            if (paramInfo.sampler) {
                                setter = make_sampler_setter(pass, parameter);
                            } else {
                                if (paramInfo.type === 'float') {
                                    setter = make_float_uniform_setter(pass, parameter);
                                } else {
                                    setter = make_int_uniform_setter(pass, parameter);
                                }
                            }

                            Object.defineProperty(this, p, {
                                set: setter,
                                enumerable: false,
                                configurable: false
                            });
                        }
                    }
                }
            }

            this.checkProperties = null;
        } else {
            Object.defineProperty(this, 'device', {
                writable: true,
                enumerable: false,
                configurable: false
            });

            Object.defineProperty(this, 'version', {
                writable: false,
                enumerable: false,
                configurable: false
            });

            Object.defineProperty(this, 'name', {
                writable: false,
                enumerable: false,
                configurable: false
            });

            Object.defineProperty(this, 'id', {
                writable: false,
                enumerable: false,
                configurable: false
            });

            Object.defineProperty(this, 'passes', {
                writable: false,
                enumerable: false,
                configurable: false
            });

            Object.defineProperty(this, 'numParameters', {
                writable: false,
                enumerable: false,
                configurable: false
            });
        }
    };

    WebGLTechnique.prototype.destroy = function () {
        var passes = this.passes;
        if (passes) {
            var numPasses = passes.length;
            var n;

            for (n = 0; n < numPasses; n += 1) {
                passes[n].destroy();
            }

            passes.length = 0;

            delete this.passes;
        }

        delete this.device;
    };

    WebGLTechnique.create = function (gd, shader, name, passes) {
        var technique = new WebGLTechnique();

        technique.initialized = false;
        technique.shader = shader;
        technique.name = name;

        var numPasses = passes.length, n;
        var numParameters = 0;
        technique.passes = [];
        technique.numPasses = numPasses;
        for (n = 0; n < numPasses; n += 1) {
            var passParams = passes[n];
            if (passParams.parameters) {
                numParameters += passParams.parameters.length;
            }
            technique.passes[n] = WebGLPass.create(gd, shader, passParams);
        }

        technique.numParameters = numParameters;

        technique.device = null;

        technique.id = ++gd.counters.techniques;

        if (1 < numPasses) {
            if (gd.drawArray !== gd.drawArrayMultiPass) {
                gd.drawArray = gd.drawArrayMultiPass;
                /* debug.log("Detected technique with multiple passes, switching to multi pass support."); */
            }
        }

        return technique;
    };
    WebGLTechnique.version = 1;
    return WebGLTechnique;
})();

//
// TZWebGLShader
//
var TZWebGLShader = (function () {
    function TZWebGLShader() {
    }
    TZWebGLShader.prototype.getTechnique = function (name) {
        if (typeof name === "string") {
            return this.techniques[name];
        } else {
            var techniques = this.techniques;
            for (var t in techniques) {
                if (techniques.hasOwnProperty(t)) {
                    if (name === 0) {
                        return techniques[t];
                    } else {
                        name -= 1;
                    }
                }
            }
            return null;
        }
    };

    TZWebGLShader.prototype.getParameter = function (name) {
        if (typeof name === "string") {
            return this.parameters[name];
        } else {
            name = (name | 0);
            var parameters = this.parameters;
            for (var p in parameters) {
                if (parameters.hasOwnProperty(p)) {
                    if (name === 0) {
                        return parameters[p];
                    } else {
                        name -= 1;
                    }
                }
            }
            return null;
        }
    };

    TZWebGLShader.prototype.initialize = function (gd) {
        if (this.initialized) {
            return;
        }

        var gl = gd.gl;
        var p;

        // Check copmpiled programs as late as possible
        var shaderPrograms = this.programs;
        for (p in shaderPrograms) {
            if (shaderPrograms.hasOwnProperty(p)) {
                var compiledProgram = shaderPrograms[p];
                var compiled = gl.getShaderParameter(compiledProgram, gl.COMPILE_STATUS);
                if (!compiled) {
                    var compilerInfo = gl.getShaderInfoLog(compiledProgram);
                    (TurbulenzEngine).callOnError('Program "' + p + '" failed to compile: ' + compilerInfo);
                }
            }
        }

        // Check linked programs as late as possible
        var linkedPrograms = this.linkedPrograms;
        for (p in linkedPrograms) {
            if (linkedPrograms.hasOwnProperty(p)) {
                var linkedProgram = linkedPrograms[p];
                var glProgram = linkedProgram.glProgram;
                if (glProgram) {
                    var linked = gl.getProgramParameter(glProgram, gl.LINK_STATUS);
                    if (!linked) {
                        var linkerInfo = gl.getProgramInfoLog(glProgram);
                        (TurbulenzEngine).callOnError('Program "' + p + '" failed to link: ' + linkerInfo);
                    }
                }
            }
        }

        this.initialized = true;
    };

    TZWebGLShader.prototype.destroy = function () {
        var gd = this.gd;
        if (gd) {
            var gl = gd.gl;
            var p;

            var techniques = this.techniques;
            if (techniques) {
                for (p in techniques) {
                    if (techniques.hasOwnProperty(p)) {
                        techniques[p].destroy();
                    }
                }
                delete this.techniques;
            }

            var linkedPrograms = this.linkedPrograms;
            if (linkedPrograms) {
                if (gl) {
                    for (p in linkedPrograms) {
                        if (linkedPrograms.hasOwnProperty(p)) {
                            var linkedProgram = linkedPrograms[p];
                            var glProgram = linkedProgram.glProgram;
                            if (glProgram) {
                                gl.deleteProgram(glProgram);
                                delete linkedProgram.glProgram;
                            }
                        }
                    }
                }
                delete this.linkedPrograms;
            }

            var programs = this.programs;
            if (programs) {
                if (gl) {
                    for (p in programs) {
                        if (programs.hasOwnProperty(p)) {
                            gl.deleteShader(programs[p]);
                        }
                    }
                }
                delete this.programs;
            }

            delete this.samplers;
            delete this.parameters;
            delete this.gd;
        }
    };

    TZWebGLShader.create = function (gd, params) {
        var gl = gd.gl;

        var shader = new TZWebGLShader();

        shader.initialized = false;

        var techniques = params.techniques;
        var parameters = params.parameters;
        var programs = params.programs;
        var samplers = params.samplers;
        var p;

        shader.gd = gd;
        shader.name = params.name;

        // Compile programs as early as possible
        var shaderPrograms = {};
        shader.programs = shaderPrograms;
        for (p in programs) {
            if (programs.hasOwnProperty(p)) {
                var program = programs[p];

                var glShaderType;
                if (program.type === 'fragment') {
                    glShaderType = gl.FRAGMENT_SHADER;
                } else if (program.type === 'vertex') {
                    glShaderType = gl.VERTEX_SHADER;
                }
                var glShader = gl.createShader(glShaderType);

                var code = program.code;

                if (gd.fixIE) {
                    code = code.replace(/#.*\n/g, '');
                    code = code.replace(/TZ_LOWP/g, '');
                    if (-1 !== code.indexOf('texture2DProj')) {
                        code = 'vec4 texture2DProj(sampler2D s, vec3 uv){ return texture2D(s, uv.xy / uv.z); }\n' + code;
                    }
                }

                gl.shaderSource(glShader, code);

                gl.compileShader(glShader);

                shaderPrograms[p] = glShader;
            }
        }

        var linkedPrograms = {};
        shader.linkedPrograms = linkedPrograms;

        // Samplers
        var defaultSampler = gd.DEFAULT_SAMPLER;
        var maxAnisotropy = gd.maxAnisotropy;

        shader.samplers = {};
        var sampler;
        for (p in samplers) {
            if (samplers.hasOwnProperty(p)) {
                sampler = samplers[p];

                var samplerMaxAnisotropy = sampler.MaxAnisotropy;
                if (samplerMaxAnisotropy) {
                    if (samplerMaxAnisotropy > maxAnisotropy) {
                        samplerMaxAnisotropy = maxAnisotropy;
                    }
                } else {
                    samplerMaxAnisotropy = defaultSampler.maxAnisotropy;
                }

                sampler = {
                    minFilter: (sampler.MinFilter || defaultSampler.minFilter),
                    magFilter: (sampler.MagFilter || defaultSampler.magFilter),
                    wrapS: (sampler.WrapS || defaultSampler.wrapS),
                    wrapT: (sampler.WrapT || defaultSampler.wrapT),
                    wrapR: (sampler.WrapR || defaultSampler.wrapR),
                    maxAnisotropy: samplerMaxAnisotropy
                };
                if (sampler.wrapS === 0x2900) {
                    sampler.wrapS = gl.CLAMP_TO_EDGE;
                }
                if (sampler.wrapT === 0x2900) {
                    sampler.wrapT = gl.CLAMP_TO_EDGE;
                }
                if (sampler.wrapR === 0x2900) {
                    sampler.wrapR = gl.CLAMP_TO_EDGE;
                }
                shader.samplers[p] = gd.createSampler(sampler);
            }
        }

        // Parameters
        var numParameters = 0;
        shader.parameters = {};
        for (p in parameters) {
            if (parameters.hasOwnProperty(p)) {
                var parameter = parameters[p];
                if (!parameter.columns) {
                    parameter.columns = 1;
                }
                if (!parameter.rows) {
                    parameter.rows = 1;
                }
                parameter.numValues = (parameter.columns * parameter.rows);
                var parameterType = parameter.type;
                if (parameterType === "float" || parameterType === "int" || parameterType === "bool") {
                    var parameterValues = parameter.values;
                    if (parameterValues) {
                        if (parameterType === "float") {
                            parameter.values = new Float32Array(parameterValues);
                        } else {
                            parameter.values = new Int32Array(parameterValues);
                        }
                    } else {
                        if (parameterType === "float") {
                            parameter.values = new Float32Array(parameter.numValues);
                        } else {
                            parameter.values = new Int32Array(parameter.numValues);
                        }
                    }
                    parameter.sampler = undefined;
                } else {
                    sampler = shader.samplers[p];
                    if (!sampler) {
                        sampler = defaultSampler;
                        shader.samplers[p] = defaultSampler;
                    }
                    parameter.sampler = sampler;
                    parameter.values = null;
                }

                parameter.name = p;

                shader.parameters[p] = parameter;
                numParameters += 1;
            }
        }
        shader.numParameters = numParameters;

        // Techniques and passes
        var shaderTechniques = {};
        var numTechniques = 0;
        shader.techniques = shaderTechniques;
        for (p in techniques) {
            if (techniques.hasOwnProperty(p)) {
                shaderTechniques[p] = WebGLTechnique.create(gd, shader, p, techniques[p]);
                numTechniques += 1;
            }
        }
        shader.numTechniques = numTechniques;

        shader.id = ++gd.counters.shaders;

        return shader;
    };
    TZWebGLShader.version = 1;
    return TZWebGLShader;
})();

//
// WebGLTechniqueParameters
//
var WebGLTechniqueParameters = (function () {
    function WebGLTechniqueParameters() {
    }
    WebGLTechniqueParameters.create = function (params) {
        var techniqueParameters = new WebGLTechniqueParameters();

        if (params) {
            for (var p in params) {
                if (params.hasOwnProperty(p)) {
                    techniqueParameters[p] = params[p];
                }
            }
        }

        return techniqueParameters;
    };
    return WebGLTechniqueParameters;
})();

//
// TechniqueParameterBuffer
//
var techniqueParameterBufferCreate = function techniqueParameterBufferCreateFn(params) {
    if (Float32Array.prototype.map === undefined) {
        Float32Array.prototype.map = function techniqueParameterBufferMap(offset, numFloats) {
            if (offset === undefined) {
                offset = 0;
            }
            var buffer = this;
            if (numFloats === undefined) {
                numFloats = this.length;
            }
            function techniqueParameterBufferWriter() {
                var numArguments = arguments.length;
                for (var a = 0; a < numArguments; a += 1) {
                    var value = arguments[a];
                    if (typeof value === 'number') {
                        buffer[offset] = value;
                        offset += 1;
                    } else {
                        buffer.setData(value, offset, value.length);
                        offset += value.length;
                    }
                }
            }
            return techniqueParameterBufferWriter;
        };

        Float32Array.prototype.unmap = function techniqueParameterBufferUnmap(writer) {
        };

        Float32Array.prototype.setData = function techniqueParameterBufferSetData(data, offset, numValues) {
            if (offset === undefined) {
                offset = 0;
            }
            if (numValues === undefined) {
                numValues = this.length;
            }
            for (var n = 0; n < numValues; n += 1, offset += 1) {
                this[offset] = data[n];
            }
        };
    }

    return new Float32Array(params.numFloats);
};

//
// WebGLDrawParameters
//
var WebGLDrawParameters = (function () {
    function WebGLDrawParameters() {
        // Streams, TechniqueParameters and Instances are stored as indexed properties
        this.sortKey = 0;
        this.technique = null;
        this.endStreams = 0;
        this.endTechniqueParameters = (16 * 3);
        this.endInstances = ((16 * 3) + 8);
        this.indexBuffer = null;
        this.primitive = -1;
        this.count = 0;
        this.firstIndex = 0;
        this.userData = null;

        // Initialize for 1 Stream
        this[0] = null;
        this[1] = null;
        this[2] = 0;

        // Initialize for 2 TechniqueParameters
        this[(16 * 3) + 0] = null;
        this[(16 * 3) + 1] = null;

        /*
        // Initialize for 8 instances
        this[((16 * 3) + 8) + 0] = undefined;
        this[((16 * 3) + 8) + 1] = undefined;
        this[((16 * 3) + 8) + 2] = undefined;
        this[((16 * 3) + 8) + 3] = undefined;
        this[((16 * 3) + 8) + 4] = undefined;
        this[((16 * 3) + 8) + 5] = undefined;
        this[((16 * 3) + 8) + 6] = undefined;
        this[((16 * 3) + 8) + 7] = undefined;
        */
        return this;
    }
    WebGLDrawParameters.prototype.setTechniqueParameters = function (indx, techniqueParameters) {
        if (indx < 8) {
            indx += (16 * 3);

            this[indx] = techniqueParameters;

            var endTechniqueParameters = this.endTechniqueParameters;
            if (techniqueParameters) {
                if (endTechniqueParameters <= indx) {
                    this.endTechniqueParameters = (indx + 1);
                }
            } else {
                while ((16 * 3) < endTechniqueParameters && !this[endTechniqueParameters - 1]) {
                    endTechniqueParameters -= 1;
                }
                this.endTechniqueParameters = endTechniqueParameters;
            }
        }
    };

    WebGLDrawParameters.prototype.setVertexBuffer = function (indx, vertexBuffer) {
        if (indx < 16) {
            indx *= 3;

            this[indx] = vertexBuffer;

            var endStreams = this.endStreams;
            if (vertexBuffer) {
                if (endStreams <= indx) {
                    this.endStreams = (indx + 3);
                }
            } else {
                while (0 < endStreams && !this[endStreams - 3]) {
                    endStreams -= 3;
                }
                this.endStreams = endStreams;
            }
        }
    };

    WebGLDrawParameters.prototype.setSemantics = function (indx, semantics) {
        if (indx < 16) {
            this[(indx * 3) + 1] = semantics;
        }
    };

    WebGLDrawParameters.prototype.setOffset = function (indx, offset) {
        if (indx < 16) {
            this[(indx * 3) + 2] = offset;
        }
    };

    WebGLDrawParameters.prototype.getTechniqueParameters = function (indx) {
        if (indx < 8) {
            return this[indx + (16 * 3)];
        } else {
            return undefined;
        }
    };

    WebGLDrawParameters.prototype.getVertexBuffer = function (indx) {
        if (indx < 16) {
            return this[(indx * 3) + 0];
        } else {
            return undefined;
        }
    };

    WebGLDrawParameters.prototype.getSemantics = function (indx) {
        if (indx < 16) {
            return this[(indx * 3) + 1];
        } else {
            return undefined;
        }
    };

    WebGLDrawParameters.prototype.getOffset = function (indx) {
        if (indx < 16) {
            return this[(indx * 3) + 2];
        } else {
            return undefined;
        }
    };

    WebGLDrawParameters.prototype.addInstance = function (instanceParameters) {
        if (instanceParameters) {
            var endInstances = this.endInstances;
            this.endInstances = (endInstances + 1);
            this[endInstances] = instanceParameters;
        }
    };

    WebGLDrawParameters.prototype.removeInstances = function () {
        this.endInstances = ((16 * 3) + 8);
    };

    WebGLDrawParameters.prototype.getNumInstances = function () {
        return (this.endInstances - ((16 * 3) + 8));
    };

    WebGLDrawParameters.create = function () {
        return new WebGLDrawParameters();
    };
    WebGLDrawParameters.version = 1;
    return WebGLDrawParameters;
})();

;

;

;

;

var WebGLGraphicsDevice = (function () {
    function WebGLGraphicsDevice() {
    }
    WebGLGraphicsDevice.prototype.drawIndexed = function (primitive, numIndices, first) {
        var gl = this.gl;
        var indexBuffer = this.activeIndexBuffer;

        var offset;
        if (first) {
            offset = (first * indexBuffer.stride);
        } else {
            offset = 0;
        }

        var format = indexBuffer.format;

        var attributeMask = this.attributeMask;

        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var numPasses = passes.length;
        var mask;

        if (activeTechnique.checkProperties) {
            activeTechnique.checkProperties(this);
        }

        if (1 === numPasses) {
            mask = (passes[0].semanticsMask & attributeMask);
            if (mask !== this.clientStateMask) {
                this.enableClientState(mask);
            }

            gl.drawElements(primitive, numIndices, format, offset);

            /* if (debug) {
                this.metrics.addPrimitives(primitive, numIndices);
            } */
        } else {
            for (var p = 0; p < numPasses; p += 1) {
                var pass = passes[p];

                mask = (pass.semanticsMask & attributeMask);
                if (mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }

                this.setPass(pass);

                gl.drawElements(primitive, numIndices, format, offset);

                /* if (debug) {
                    this.metrics.addPrimitives(primitive, numIndices);
                } */
            }
        }
    };

    WebGLGraphicsDevice.prototype.draw = function (primitive, numVertices, first) {
        var gl = this.gl;

        var attributeMask = this.attributeMask;

        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var numPasses = passes.length;
        var mask;

        if (activeTechnique.checkProperties) {
            activeTechnique.checkProperties(this);
        }

        if (1 === numPasses) {
            mask = (passes[0].semanticsMask & attributeMask);
            if (mask !== this.clientStateMask) {
                this.enableClientState(mask);
            }

            gl.drawArrays(primitive, first, numVertices);

            /* if (debug) {
                this.metrics.addPrimitives(primitive, numVertices);
            } */
        } else {
            for (var p = 0; p < numPasses; p += 1) {
                var pass = passes[p];

                mask = (pass.semanticsMask & attributeMask);
                if (mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }

                this.setPass(pass);

                gl.drawArrays(primitive, first, numVertices);

                /* if (debug) {
                    this.metrics.addPrimitives(primitive, numVertices);
                } */
            }
        }
    };

    WebGLGraphicsDevice.prototype.setTechniqueParameters = function () {
        var activeTechnique = this.activeTechnique;
        var passes = activeTechnique.passes;
        var numTechniqueParameters = arguments.length;
        if (1 === passes.length) {
            var parameters = passes[0].parameters;
            for (var t = 0; t < numTechniqueParameters; t += 1) {
                this.setParametersImmediate(parameters, arguments[t]);
            }
        } else {
            for (var t = 0; t < numTechniqueParameters; t += 1) {
                this.setParametersDeferred(this, passes, arguments[t]);
            }
        }
    };

    //Internal
    WebGLGraphicsDevice.prototype.setParametersImmediate = function (parameters, techniqueParameters) {
        var gl = this.gl;

        for (var p in techniqueParameters) {
            var parameter = parameters[p];
            if (parameter !== undefined) {
                var parameterValues = techniqueParameters[p];
                if (parameterValues !== undefined) {
                    var paramInfo = parameter.info;
                    var numColumns, location;
                    if (paramInfo.type === 'float') {
                        numColumns = paramInfo.columns;
                        location = parameter.location;
                        if (4 === numColumns) {
                            gl.uniform4fv(location, parameterValues);
                        } else if (3 === numColumns) {
                            gl.uniform3fv(location, parameterValues);
                        } else if (2 === numColumns) {
                            gl.uniform2fv(location, parameterValues);
                        } else if (1 === paramInfo.rows) {
                            gl.uniform1f(location, parameterValues);
                        } else {
                            gl.uniform1fv(location, parameterValues);
                        }
                    } else if (paramInfo.sampler !== undefined) {
                        this.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                    } else {
                        numColumns = paramInfo.columns;
                        location = parameter.location;
                        if (4 === numColumns) {
                            gl.uniform4iv(location, parameterValues);
                        } else if (3 === numColumns) {
                            gl.uniform3iv(location, parameterValues);
                        } else if (2 === numColumns) {
                            gl.uniform2iv(location, parameterValues);
                        } else if (1 === paramInfo.rows) {
                            gl.uniform1i(location, parameterValues);
                        } else {
                            gl.uniform1iv(location, parameterValues);
                        }
                    }
                } else {
                    delete techniqueParameters[p];
                }
            }
        }
    };

    // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY
    WebGLGraphicsDevice.prototype.setParametersCaching = function (parameters, techniqueParameters) {
        var gl = this.gl;

        for (var p in techniqueParameters) {
            var parameter = parameters[p];
            if (parameter !== undefined) {
                var parameterValues = techniqueParameters[p];
                if (parameter.value !== parameterValues) {
                    if (parameterValues !== undefined) {
                        parameter.value = parameterValues;

                        var paramInfo = parameter.info;
                        var numColumns, location;
                        if (paramInfo.type === 'float') {
                            numColumns = paramInfo.columns;
                            location = parameter.location;
                            if (4 === numColumns) {
                                gl.uniform4fv(location, parameterValues);
                            } else if (3 === numColumns) {
                                gl.uniform3fv(location, parameterValues);
                            } else if (2 === numColumns) {
                                gl.uniform2fv(location, parameterValues);
                            } else if (1 === paramInfo.rows) {
                                gl.uniform1f(location, parameterValues);
                            } else {
                                gl.uniform1fv(location, parameterValues);
                            }
                        } else if (paramInfo.sampler !== undefined) {
                            this.setTexture(parameter.textureUnit, parameterValues, paramInfo.sampler);
                        } else {
                            numColumns = paramInfo.columns;
                            location = parameter.location;
                            if (4 === numColumns) {
                                gl.uniform4iv(location, parameterValues);
                            } else if (3 === numColumns) {
                                gl.uniform3iv(location, parameterValues);
                            } else if (2 === numColumns) {
                                gl.uniform2iv(location, parameterValues);
                            } else if (1 === paramInfo.rows) {
                                gl.uniform1i(location, parameterValues);
                            } else {
                                gl.uniform1iv(location, parameterValues);
                            }
                        }
                    } else {
                        delete techniqueParameters[p];
                    }
                }
            }
        }
    };

    // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAYMULTIPASS
    WebGLGraphicsDevice.prototype.setParametersCachingMultiPass = function (gd, passes, techniqueParameters) {
        gd.setParametersCaching(passes[0].parameters, techniqueParameters);
    };

    WebGLGraphicsDevice.prototype.setParametersDeferred = function (gd, passes, techniqueParameters) {
        var numPasses = passes.length;
        var min = Math.min;
        var max = Math.max;
        for (var n = 0; n < numPasses; n += 1) {
            var pass = passes[n];
            var parameters = pass.parameters;
            pass.dirty = true;

            for (var p in techniqueParameters) {
                var parameter = parameters[p];
                if (parameter) {
                    var parameterValues = techniqueParameters[p];
                    if (parameterValues !== undefined) {
                        var paramInfo = parameter.info;
                        if (paramInfo.sampler) {
                            paramInfo.values = parameterValues;
                            parameter.dirty = 1;
                        } else if (typeof parameterValues !== 'number') {
                            var values = paramInfo.values;
                            var numValues = min(paramInfo.numValues, parameterValues.length);
                            for (var v = 0; v < numValues; v += 1) {
                                values[v] = parameterValues[v];
                            }
                            parameter.dirty = max(numValues, (parameter.dirty || 0));
                        } else {
                            paramInfo.values[0] = parameterValues;
                            parameter.dirty = (parameter.dirty || 1);
                        }
                    } else {
                        delete techniqueParameters[p];
                    }
                }
            }
        }
    };

    WebGLGraphicsDevice.prototype.setTechnique = function (technique) {
        var activeTechnique = this.activeTechnique;
        if (activeTechnique !== technique) {
            if (activeTechnique) {
                activeTechnique.deactivate();
            }

            this.activeTechnique = technique;

            technique.activate(this);

            var passes = technique.passes;
            if (1 === passes.length) {
                this.setPass(passes[0]);
            }
        }
    };

    // ONLY USE FOR SINGLE PASS TECHNIQUES ON DRAWARRAY
    WebGLGraphicsDevice.prototype.setTechniqueCaching = function (technique) {
        var pass = technique.passes[0];

        var activeTechnique = this.activeTechnique;
        if (activeTechnique !== technique) {
            if (activeTechnique) {
                activeTechnique.deactivate();
            }

            this.activeTechnique = technique;

            technique.activate(this);

            this.setPass(pass);
        }

        var parameters = pass.parameters;
        for (var p in parameters) {
            if (parameters.hasOwnProperty(p)) {
                parameters[p].value = null;
            }
        }
    };

    WebGLGraphicsDevice.prototype.setStream = function (vertexBuffer, semantics, offset) {
        /* if (debug) {
            debug.assert(vertexBuffer instanceof WebGLVertexBuffer);
            debug.assert(semantics instanceof WebGLSemantics);
        } */

        if (offset) {
            offset *= (vertexBuffer).strideInBytes;
        } else {
            offset = 0;
        }

        this.bindVertexBuffer((vertexBuffer).glBuffer);

        var attributes = semantics;
        var numAttributes = attributes.length;
        if (numAttributes > (vertexBuffer).numAttributes) {
            numAttributes = (vertexBuffer).numAttributes;
        }

        this.attributeMask |= (vertexBuffer).bindAttributes(numAttributes, attributes, offset);
    };

    WebGLGraphicsDevice.prototype.setIndexBuffer = function (indexBuffer) {
        if (this.activeIndexBuffer !== indexBuffer) {
            this.activeIndexBuffer = indexBuffer;
            var glBuffer;
            if (indexBuffer) {
                glBuffer = (indexBuffer).glBuffer;
            } else {
                glBuffer = null;
            }
            var gl = this.gl;
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, glBuffer);

            /* if (debug) {
                this.metrics.indexBufferChanges += 1;
            } */
        }
    };

    // This version only support technique with a single pass, but it is faster
    WebGLGraphicsDevice.prototype.drawArray = function (drawParametersArray, globalTechniqueParametersArray, sortMode) {
        var gl = this.gl;
        var ELEMENT_ARRAY_BUFFER = gl.ELEMENT_ARRAY_BUFFER;

        var numGlobalTechniqueParameters = globalTechniqueParametersArray.length;

        var numDrawParameters = drawParametersArray.length;
        if (numDrawParameters > 1 && sortMode) {
            if (sortMode > 0) {
                drawParametersArray.sort(this._drawArraySortPositive);
            } else {
                drawParametersArray.sort(this._drawArraySortNegative);
            }
        }

        var activeIndexBuffer = this.activeIndexBuffer;
        var attributeMask = this.attributeMask;
        var lastTechnique = null;
        var lastEndStreams = -1;
        var lastDrawParameters = null;
        var techniqueParameters = null;
        var v = 0;
        var streamsMatch = false;
        var vertexBuffer = null;
        var pass = null;
        var passParameters = null;
        var p = null;
        var indexFormat = 0;
        var indexStride = 0;
        var mask = 0;
        var t = 0;

        if (activeIndexBuffer) {
            indexFormat = activeIndexBuffer.format;
            indexStride = activeIndexBuffer.stride;
        }

        for (var n = 0; n < numDrawParameters; n += 1) {
            var drawParameters = drawParametersArray[n];
            var technique = drawParameters.technique;
            var endTechniqueParameters = drawParameters.endTechniqueParameters;
            var endStreams = drawParameters.endStreams;
            var endInstances = drawParameters.endInstances;
            var indexBuffer = drawParameters.indexBuffer;
            var primitive = drawParameters.primitive;
            var count = drawParameters.count;
            var firstIndex = drawParameters.firstIndex;

            if (lastTechnique !== technique) {
                lastTechnique = technique;

                this.setTechniqueCaching(technique);

                pass = technique.passes[0];
                passParameters = pass.parameters;

                mask = (pass.semanticsMask & attributeMask);
                if (mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }

                if (technique.checkProperties) {
                    technique.checkProperties(this);
                }

                for (t = 0; t < numGlobalTechniqueParameters; t += 1) {
                    this.setParametersCaching(passParameters, globalTechniqueParametersArray[t]);
                }
            }

            for (t = (16 * 3); t < endTechniqueParameters; t += 1) {
                techniqueParameters = drawParameters[t];
                if (techniqueParameters) {
                    this.setParametersCaching(passParameters, techniqueParameters);
                }
            }

            streamsMatch = (lastEndStreams === endStreams);
            for (v = 0; streamsMatch && v < endStreams; v += 3) {
                streamsMatch = (lastDrawParameters[v] === drawParameters[v] && lastDrawParameters[v + 1] === drawParameters[v + 1] && lastDrawParameters[v + 2] === drawParameters[v + 2]);
            }

            if (!streamsMatch) {
                lastEndStreams = endStreams;

                for (v = 0; v < endStreams; v += 3) {
                    vertexBuffer = drawParameters[v];
                    if (vertexBuffer) {
                        this.setStream(vertexBuffer, drawParameters[v + 1], drawParameters[v + 2]);
                    }
                }

                attributeMask = this.attributeMask;

                mask = (pass.semanticsMask & attributeMask);
                if (mask !== this.clientStateMask) {
                    this.enableClientState(mask);
                }
            }

            lastDrawParameters = drawParameters;

            if (indexBuffer) {
                if (activeIndexBuffer !== indexBuffer) {
                    activeIndexBuffer = indexBuffer;
                    gl.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer.glBuffer);

                    indexFormat = indexBuffer.format;
                    indexStride = indexBuffer.stride;

                    /* if (debug) {
                        this.metrics.indexBufferChanges += 1;
                    } */
                }

                firstIndex *= indexStride;

                t = ((16 * 3) + 8);
                if (t < endInstances) {
                    do {
                        this.setParametersCaching(passParameters, drawParameters[t]);

                        gl.drawElements(primitive, count, indexFormat, firstIndex);

                        /* if (debug) {
                            this.metrics.addPrimitives(primitive, count);
                        } */

                        t += 1;
                    } while(t < endInstances);
                } else {
                    gl.drawElements(primitive, count, indexFormat, firstIndex);

                    /* if (debug) {
                        this.metrics.addPrimitives(primitive, count);
                    } */
                }
            } else {
                t = ((16 * 3) + 8);
                if (t < endInstances) {
                    do {
                        this.setParametersCaching(passParameters, drawParameters[t]);

                        gl.drawArrays(primitive, firstIndex, count);

                        /* if (debug) {
                            this.metrics.addPrimitives(primitive, count);
                        } */

                        t += 1;
                    } while(t < endInstances);
                } else {
                    gl.drawArrays(primitive, firstIndex, count);

                    /* if (debug) {
                        this.metrics.addPrimitives(primitive, count);
                    } */
                }
            }
            /*jshint bitwise: true*/
        }

        this.activeIndexBuffer = activeIndexBuffer;
    };

    // This version suports technique with multiple passes but it is slower
    WebGLGraphicsDevice.prototype.drawArrayMultiPass = function (drawParametersArray, globalTechniqueParametersArray, sortMode) {
        var gl = this.gl;
        var ELEMENT_ARRAY_BUFFER = gl.ELEMENT_ARRAY_BUFFER;

        var setParametersCaching = this.setParametersCachingMultiPass;
        var setParametersDeferred = this.setParametersDeferred;

        var numGlobalTechniqueParameters = globalTechniqueParametersArray.length;

        var numDrawParameters = drawParametersArray.length;
        if (numDrawParameters > 1 && sortMode) {
            if (sortMode > 0) {
                drawParametersArray.sort(this._drawArraySortPositive);
            } else {
                drawParametersArray.sort(this._drawArraySortNegative);
            }
        }

        var activeIndexBuffer = this.activeIndexBuffer;
        var attributeMask = this.attributeMask;
        var setParameters = null;
        var lastTechnique = null;
        var lastEndStreams = -1;
        var lastDrawParameters = null;
        var techniqueParameters = null;
        var v = 0;
        var streamsMatch = false;
        var vertexBuffer = null;
        var passes = null;
        var p = null;
        var pass = null;
        var indexFormat = 0;
        var indexStride = 0;
        var numPasses = 0;
        var mask = 0;
        var t = 0;

        if (activeIndexBuffer) {
            indexFormat = activeIndexBuffer.format;
            indexStride = activeIndexBuffer.stride;
        }

        for (var n = 0; n < numDrawParameters; n += 1) {
            var drawParameters = (drawParametersArray[n]);
            var technique = drawParameters.technique;
            var endTechniqueParameters = drawParameters.endTechniqueParameters;
            var endStreams = drawParameters.endStreams;
            var endInstances = drawParameters.endInstances;
            var indexBuffer = drawParameters.indexBuffer;
            var primitive = drawParameters.primitive;
            var count = drawParameters.count;
            var firstIndex = drawParameters.firstIndex;

            if (lastTechnique !== technique) {
                lastTechnique = technique;

                passes = technique.passes;
                numPasses = passes.length;
                if (1 === numPasses) {
                    this.setTechniqueCaching(technique);
                    setParameters = setParametersCaching;

                    mask = (passes[0].semanticsMask & attributeMask);
                    if (mask !== this.clientStateMask) {
                        this.enableClientState(mask);
                    }
                } else {
                    this.setTechnique(technique);
                    setParameters = setParametersDeferred;
                }

                if (technique.checkProperties) {
                    technique.checkProperties(this);
                }

                for (t = 0; t < numGlobalTechniqueParameters; t += 1) {
                    setParameters(this, passes, globalTechniqueParametersArray[t]);
                }
            }

            for (t = (16 * 3); t < endTechniqueParameters; t += 1) {
                techniqueParameters = drawParameters[t];
                if (techniqueParameters) {
                    setParameters(this, passes, techniqueParameters);
                }
            }

            streamsMatch = (lastEndStreams === endStreams);
            for (v = 0; streamsMatch && v < endStreams; v += 3) {
                streamsMatch = (lastDrawParameters[v] === drawParameters[v] && lastDrawParameters[v + 1] === drawParameters[v + 1] && lastDrawParameters[v + 2] === drawParameters[v + 2]);
            }

            if (!streamsMatch) {
                lastEndStreams = endStreams;

                for (v = 0; v < endStreams; v += 3) {
                    vertexBuffer = drawParameters[v];
                    if (vertexBuffer) {
                        this.setStream(vertexBuffer, drawParameters[v + 1], drawParameters[v + 2]);
                    }
                }

                attributeMask = this.attributeMask;
                if (1 === numPasses) {
                    mask = (passes[0].semanticsMask & attributeMask);
                    if (mask !== this.clientStateMask) {
                        this.enableClientState(mask);
                    }
                }
            }

            lastDrawParameters = drawParameters;

            if (indexBuffer) {
                if (activeIndexBuffer !== indexBuffer) {
                    activeIndexBuffer = indexBuffer;
                    gl.bindBuffer(ELEMENT_ARRAY_BUFFER, indexBuffer.glBuffer);

                    indexFormat = indexBuffer.format;
                    indexStride = indexBuffer.stride;

                    /* if (debug) {
                        this.metrics.indexBufferChanges += 1;
                    } */
                }

                firstIndex *= indexStride;

                if (1 === numPasses) {
                    t = ((16 * 3) + 8);
                    if (t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);

                            gl.drawElements(primitive, count, indexFormat, firstIndex);

                            /* if (debug) {
                                this.metrics.addPrimitives(primitive, count);
                            } */

                            t += 1;
                        } while(t < endInstances);
                    } else {
                        gl.drawElements(primitive, count, indexFormat, firstIndex);

                        /* if (debug) {
                            this.metrics.addPrimitives(primitive, count);
                        } */
                    }
                } else {
                    t = ((16 * 3) + 8);
                    if (t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);

                            for (p = 0; p < numPasses; p += 1) {
                                pass = passes[p];

                                mask = (pass.semanticsMask & attributeMask);
                                if (mask !== this.clientStateMask) {
                                    this.enableClientState(mask);
                                }

                                this.setPass(pass);

                                gl.drawElements(primitive, count, indexFormat, firstIndex);

                                /* if (debug) {
                                    this.metrics.addPrimitives(primitive, count);
                                } */
                            }

                            t += 1;
                        } while(t < endInstances);
                    } else {
                        for (p = 0; p < numPasses; p += 1) {
                            pass = passes[p];

                            mask = (pass.semanticsMask & attributeMask);
                            if (mask !== this.clientStateMask) {
                                this.enableClientState(mask);
                            }

                            this.setPass(pass);

                            gl.drawElements(primitive, count, indexFormat, firstIndex);

                            /* if (debug) {
                                this.metrics.addPrimitives(primitive, count);
                            } */
                        }
                    }
                }
            } else {
                if (1 === numPasses) {
                    t = ((16 * 3) + 8);
                    if (t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);

                            gl.drawArrays(primitive, firstIndex, count);

                            /* if (debug) {
                                this.metrics.addPrimitives(primitive, count);
                            } */

                            t += 1;
                        } while(t < endInstances);
                    } else {
                        gl.drawArrays(primitive, firstIndex, count);

                        /* if (debug) {
                            this.metrics.addPrimitives(primitive, count);
                        } */
                    }
                } else {
                    t = ((16 * 3) + 8);
                    if (t < endInstances) {
                        do {
                            setParameters(this, passes, drawParameters[t]);

                            for (p = 0; p < numPasses; p += 1) {
                                pass = passes[p];

                                mask = (pass.semanticsMask & attributeMask);
                                if (mask !== this.clientStateMask) {
                                    this.enableClientState(mask);
                                }

                                this.setPass(pass);

                                gl.drawArrays(primitive, firstIndex, count);
                            }

                            /* if (debug) {
                                this.metrics.addPrimitives(primitive, count);
                            } */

                            t += 1;
                        } while(t < endInstances);
                    } else {
                        for (p = 0; p < numPasses; p += 1) {
                            pass = passes[p];

                            mask = (pass.semanticsMask & attributeMask);
                            if (mask !== this.clientStateMask) {
                                this.enableClientState(mask);
                            }

                            this.setPass(pass);

                            gl.drawArrays(primitive, firstIndex, count);

                            /* if (debug) {
                                this.metrics.addPrimitives(primitive, count);
                            } */
                        }
                    }
                }
            }
            /*jshint bitwise: true*/
        }

        this.activeIndexBuffer = activeIndexBuffer;
    };

    WebGLGraphicsDevice.prototype.beginDraw = function (primitive, numVertices, formats, semantics) {
        this.immediatePrimitive = primitive;
        if (numVertices) {
            var n;
            var immediateSemantics = this.immediateSemantics;
            var attributes = semantics;
            var numAttributes = attributes.length;
            immediateSemantics.length = numAttributes;
            for (n = 0; n < numAttributes; n += 1) {
                var attribute = attributes[n];
                if (typeof attribute === "string") {
                    attribute = this['SEMANTIC_' + attribute];
                }
                immediateSemantics[n] = attribute;
            }

            var immediateVertexBuffer = this.immediateVertexBuffer;

            var oldStride = immediateVertexBuffer.strideInBytes;
            var oldSize = (oldStride * immediateVertexBuffer.numVertices);

            var stride = immediateVertexBuffer.setAttributes(formats);
            if (stride !== oldStride) {
                immediateVertexBuffer.numVertices = Math.floor(oldSize / stride);
            }

            var size = (stride * numVertices);
            if (size > oldSize) {
                immediateVertexBuffer.resize(size);
            }

            return immediateVertexBuffer.map(0, numVertices);
        }
        return null;
    };

    WebGLGraphicsDevice.prototype.endDraw = function (writer) {
        var immediateVertexBuffer = this.immediateVertexBuffer;

        var numVerticesWritten = writer.getNumWrittenVertices();

        immediateVertexBuffer.unmap(writer);

        if (numVerticesWritten) {
            var gl = this.gl;

            var stride = immediateVertexBuffer.strideInBytes;
            var offset = 0;

            var vertexAttributes = immediateVertexBuffer.attributes;

            var semantics = this.immediateSemantics;
            var numSemantics = semantics.length;
            var deltaAttributeMask = 0;
            for (var n = 0; n < numSemantics; n += 1) {
                var vertexAttribute = vertexAttributes[n];

                var attribute = semantics[n];

                deltaAttributeMask |= (1 << attribute);

                gl.vertexAttribPointer(attribute, vertexAttribute.numComponents, vertexAttribute.format, vertexAttribute.normalized, stride, offset);

                offset += vertexAttribute.stride;
            }
            this.attributeMask |= deltaAttributeMask;

            this.draw(this.immediatePrimitive, numVerticesWritten, 0);
        }
    };

    WebGLGraphicsDevice.prototype.setViewport = function (x, y, w, h) {
        var currentBox = this.state.viewportBox;
        if (currentBox[0] !== x || currentBox[1] !== y || currentBox[2] !== w || currentBox[3] !== h) {
            currentBox[0] = x;
            currentBox[1] = y;
            currentBox[2] = w;
            currentBox[3] = h;
            this.gl.viewport(x, y, w, h);
        }
    };

    WebGLGraphicsDevice.prototype.setScissor = function (x, y, w, h) {
        var currentBox = this.state.scissorBox;
        if (currentBox[0] !== x || currentBox[1] !== y || currentBox[2] !== w || currentBox[3] !== h) {
            currentBox[0] = x;
            currentBox[1] = y;
            currentBox[2] = w;
            currentBox[3] = h;
            this.gl.scissor(x, y, w, h);
        }
    };

    WebGLGraphicsDevice.prototype.clear = function (color, depth, stencil) {
        var gl = this.gl;
        var state = this.state;

        var clearMask = 0;

        if (color) {
            clearMask += gl.COLOR_BUFFER_BIT;

            var currentColor = state.clearColor;
            var color0 = color[0];
            var color1 = color[1];
            var color2 = color[2];
            var color3 = color[3];
            if (currentColor[0] !== color0 || currentColor[1] !== color1 || currentColor[2] !== color2 || currentColor[3] !== color3) {
                currentColor[0] = color0;
                currentColor[1] = color1;
                currentColor[2] = color2;
                currentColor[3] = color3;
                gl.clearColor(color0, color1, color2, color3);
            }
        }

        if (typeof depth === 'number') {
            clearMask += gl.DEPTH_BUFFER_BIT;

            if (state.clearDepth !== depth) {
                state.clearDepth = depth;
                gl.clearDepth(depth);
            }

            if (typeof stencil === 'number') {
                clearMask += gl.STENCIL_BUFFER_BIT;

                if (state.clearStencil !== stencil) {
                    state.clearStencil = stencil;
                    gl.clearStencil(stencil);
                }
            }
        }

        if (clearMask) {
            var colorMask = state.colorMask;
            var colorMaskEnabled = (colorMask[0] || colorMask[1] || colorMask[2] || colorMask[3]);
            var depthMask = state.depthMask;
            var program = state.program;

            if (color) {
                if (!colorMaskEnabled) {
                    // This is posibly a mistake, enable it for this call
                    gl.colorMask(true, true, true, true);
                }
            }

            if (typeof depth === 'number') {
                if (!depthMask) {
                    // This is posibly a mistake, enable it for this call
                    gl.depthMask(true);
                }
            }

            if (program) {
                gl.useProgram(null);
            }

            gl.clear(clearMask);

            if (color) {
                if (!colorMaskEnabled) {
                    gl.colorMask(false, false, false, false);
                }
            }

            if (typeof depth === 'number') {
                if (!depthMask) {
                    gl.depthMask(false);
                }
            }

            if (program) {
                gl.useProgram(program);
            }
        }
    };

    WebGLGraphicsDevice.prototype.beginFrame = function () {
        var gl = this.gl;

        this.attributeMask = 0;

        var clientStateMask = this.clientStateMask;
        var n;
        if (clientStateMask) {
            for (n = 0; n < 16; n += 1) {
                if (clientStateMask & (1 << n)) {
                    gl.disableVertexAttribArray(n);
                }
            }
            this.clientStateMask = 0;
        }

        this.resetStates();

        this.setScissor(0, 0, this.width, this.height);
        this.setViewport(0, 0, this.width, this.height);

        /* if (debug) {
            this.metrics.renderTargetChanges = 0;
            this.metrics.textureChanges = 0;
            this.metrics.renderStateChanges = 0;
            this.metrics.vertexBufferChanges = 0;
            this.metrics.indexBufferChanges = 0;
            this.metrics.techniqueChanges = 0;
            this.metrics.drawCalls = 0;
            this.metrics.primitives = 0;
        } */

        return !(document.hidden || document['webkitHidden']);
    };

    WebGLGraphicsDevice.prototype.beginRenderTarget = function (renderTarget) {
        this.activeRenderTarget = renderTarget;

        /* if (debug) {
            this.metrics.renderTargetChanges += 1;
        } */

        return (renderTarget).bind();
    };

    WebGLGraphicsDevice.prototype.endRenderTarget = function () {
        this.activeRenderTarget.unbind();
        this.activeRenderTarget = null;
    };

    WebGLGraphicsDevice.prototype.beginOcclusionQuery = function () {
        return false;
    };

    WebGLGraphicsDevice.prototype.endOcclusionQuery = function () {
    };

    WebGLGraphicsDevice.prototype.endFrame = function () {
        var gl = this.gl;

        if (this.activeTechnique) {
            this.activeTechnique.deactivate();
            this.activeTechnique = null;
        }

        if (this.activeIndexBuffer) {
            this.setIndexBuffer(null);
        }

        var state = this.state;
        if (state.program) {
            state.program = null;
            gl.useProgram(null);
        }

        this.numFrames += 1;
        var currentFrameTime = TurbulenzEngine.getTime();
        var diffTime = (currentFrameTime - this.previousFrameTime);
        if (diffTime >= 1000.0) {
            this.fps = (this.numFrames / (diffTime * 0.001));
            this.numFrames = 0;
            this.previousFrameTime = currentFrameTime;
        }

        var canvas = gl.canvas;
        var width = (gl.drawingBufferWidth || canvas.width);
        var height = (gl.drawingBufferHeight || canvas.height);
        if (this.width !== width || this.height !== height) {
            this.width = width;
            this.height = height;
            this.setViewport(0, 0, width, height);
            this.setScissor(0, 0, width, height);
        }

        this.checkFullScreen();
    };

    WebGLGraphicsDevice.prototype.createTechniqueParameters = function (params) {
        return WebGLTechniqueParameters.create(params);
    };

    WebGLGraphicsDevice.prototype.createSemantics = function (attributes) {
        return WebGLSemantics.create(this, attributes);
    };

    WebGLGraphicsDevice.prototype.createVertexBuffer = function (params) {
        return WebGLVertexBuffer.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createIndexBuffer = function (params) {
        return WebGLIndexBuffer.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createTexture = function (params) {
        return TZWebGLTexture.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createVideo = function (params) {
        return WebGLVideo.create(params);
    };

    WebGLGraphicsDevice.prototype.createShader = function (params) {
        return TZWebGLShader.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createTechniqueParameterBuffer = function (params) {
        // TOOD: We're returning a float array, which doesn't have all
        // the proprties that are expected.
        return techniqueParameterBufferCreate(params);
    };

    WebGLGraphicsDevice.prototype.createRenderBuffer = function (params) {
        return WebGLRenderBuffer.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createRenderTarget = function (params) {
        return WebGLRenderTarget.create(this, params);
    };

    WebGLGraphicsDevice.prototype.createOcclusionQuery = function () {
        return null;
    };

    WebGLGraphicsDevice.prototype.createDrawParameters = function () {
        return WebGLDrawParameters.create();
    };

    WebGLGraphicsDevice.prototype.isSupported = function (name) {
        var gl = this.gl;
        if ("OCCLUSION_QUERIES" === name) {
            return false;
        } else if ("NPOT_MIPMAPPED_TEXTURES" === name) {
            return false;
        } else if ("TEXTURE_DXT1" === name || "TEXTURE_DXT3" === name || "TEXTURE_DXT5" === name) {
            var compressedTexturesExtension = this.compressedTexturesExtension;
            if (compressedTexturesExtension) {
                var compressedFormats = gl.getParameter(gl.COMPRESSED_TEXTURE_FORMATS);
                if (compressedFormats) {
                    var requestedFormat;
                    if ("TEXTURE_DXT1" === name) {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT1_EXT;
                    } else if ("TEXTURE_DXT3" === name) {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT3_EXT;
                    } else {
                        requestedFormat = compressedTexturesExtension.COMPRESSED_RGBA_S3TC_DXT5_EXT;
                    }
                    var numCompressedFormats = compressedFormats.length;
                    for (var n = 0; n < numCompressedFormats; n += 1) {
                        if (compressedFormats[n] === requestedFormat) {
                            return true;
                        }
                    }
                }
            }
            return false;
        } else if ("TEXTURE_ETC1" === name) {
            return false;
        } else if ("INDEXFORMAT_UINT" === name) {
            if (gl.getExtension('OES_element_index_uint')) {
                return true;
            }
            return false;
        } else if ("FILEFORMAT_WEBM" === name) {
            return ("webm" in this.supportedVideoExtensions);
        } else if ("FILEFORMAT_MP4" === name) {
            return ("mp4" in this.supportedVideoExtensions);
        } else if ("FILEFORMAT_JPG" === name) {
            return true;
        } else if ("FILEFORMAT_PNG" === name) {
            return true;
        } else if ("FILEFORMAT_DDS" === name) {
            return typeof DDSLoader !== 'undefined';
        } else if ("FILEFORMAT_TGA" === name) {
            return typeof TGALoader !== 'undefined';
        }
        return undefined;
    };

    WebGLGraphicsDevice.prototype.maxSupported = function (name) {
        var gl = this.gl;
        if ("ANISOTROPY" === name) {
            return this.maxAnisotropy;
        } else if ("TEXTURE_SIZE" === name) {
            return gl.getParameter(gl.MAX_TEXTURE_SIZE);
        } else if ("CUBEMAP_TEXTURE_SIZE" === name) {
            return gl.getParameter(gl.MAX_CUBE_MAP_TEXTURE_SIZE);
        } else if ("3D_TEXTURE_SIZE" === name) {
            return 0;
        } else if ("RENDERTARGET_COLOR_TEXTURES" === name) {
            if (this.drawBuffersExtension) {
                if (this.WEBGL_draw_buffers) {
                    return gl.getParameter(this.drawBuffersExtension.MAX_COLOR_ATTACHMENTS_WEBGL);
                } else {
                    return gl.getParameter(this.drawBuffersExtension.MAX_COLOR_ATTACHMENTS_EXT);
                }
            }
            return 1;
        } else if ("RENDERBUFFER_SIZE" === name) {
            return gl.getParameter(gl.MAX_RENDERBUFFER_SIZE);
        } else if ("TEXTURE_UNITS" === name) {
            return gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
        } else if ("VERTEX_TEXTURE_UNITS" === name) {
            return gl.getParameter(gl.MAX_VERTEX_TEXTURE_IMAGE_UNITS);
        } else if ("VERTEX_SHADER_PRECISION" === name || "FRAGMENT_SHADER_PRECISION" === name) {
            var shaderType;
            if ("VERTEX_SHADER_PRECISION" === name) {
                shaderType = gl.VERTEX_SHADER;
            } else {
                shaderType = gl.FRAGMENT_SHADER;
            }

            if (!gl.getShaderPrecisionFormat) {
                return 0;
            }

            var sp = gl.getShaderPrecisionFormat(shaderType, gl.HIGH_FLOAT);
            if (!sp || !sp.precision) {
                sp = gl.getShaderPrecisionFormat(shaderType, gl.MEDIUM_FLOAT);
                if (!sp || !sp.precision) {
                    sp = gl.getShaderPrecisionFormat(shaderType, gl.LOW_FLOAT);
                    if (!sp || !sp.precision) {
                        return 0;
                    }
                }
            }
            return sp.precision;
        }
        return 0;
    };

    WebGLGraphicsDevice.prototype.loadTexturesArchive = function (params) {
        var src = params.src;
        if (typeof TARLoader !== 'undefined') {
            TARLoader.create({
                gd: this,
                src: src,
                mipmaps: params.mipmaps,
                ontextureload: function tarTextureLoadedFn(texture) {
                    params.ontextureload(texture);
                },
                onload: function tarLoadedFn(success, status) {
                    if (params.onload) {
                        params.onload(success, status);
                    }
                },
                onerror: function tarFailedFn(status) {
                    if (params.onload) {
                        params.onload(false, status);
                    }
                }
            });
            return true;
        } else {
            (TurbulenzEngine).callOnError('Missing archive loader required for ' + src);
            return false;
        }
    };

    WebGLGraphicsDevice.prototype.getScreenshot = function (compress, x, y, width, height) {
        var gl = this.gl;
        var canvas = gl.canvas;

        if (compress) {
            return canvas.toDataURL('image/jpeg');
        } else {
            if (x === undefined) {
                x = 0;
            }

            if (y === undefined) {
                y = 0;
            }

            var target = this.activeRenderTarget;
            if (!target) {
                target = canvas;
            }

            if (width === undefined) {
                width = target.width;
            }

            if (height === undefined) {
                height = target.height;
            }

            var pixels = new Uint8Array(4 * width * height);

            gl.readPixels(x, y, width, height, gl.RGBA, gl.UNSIGNED_BYTE, pixels);

            return pixels;
        }
    };

    WebGLGraphicsDevice.prototype.flush = function () {
        this.gl.flush();
    };

    WebGLGraphicsDevice.prototype.finish = function () {
        this.gl.finish();
    };

    // private
    WebGLGraphicsDevice.prototype._drawArraySortPositive = function (a, b) {
        return (b.sortKey - a.sortKey);
    };

    WebGLGraphicsDevice.prototype._drawArraySortNegative = function (a, b) {
        return (a.sortKey - b.sortKey);
    };

    WebGLGraphicsDevice.prototype.checkFullScreen = function () {
        var fullscreen = this.fullscreen;
        if (this.oldFullscreen !== fullscreen) {
            this.oldFullscreen = fullscreen;

            this.requestFullScreen(fullscreen);
        }
    };

    WebGLGraphicsDevice.prototype.requestFullScreen = function (fullscreen) {
        if (fullscreen) {
            var canvas = this.gl.canvas;
            if (canvas.webkitRequestFullScreenWithKeys) {
                canvas.webkitRequestFullScreenWithKeys();
            } else if (canvas.requestFullScreenWithKeys) {
                canvas.requestFullScreenWithKeys();
            } else if (canvas.webkitRequestFullScreen) {
                canvas.webkitRequestFullScreen(canvas.ALLOW_KEYBOARD_INPUT);
            } else if (canvas.mozRequestFullScreen) {
                canvas.mozRequestFullScreen();
            } else if (canvas.msRequestFullscreen) {
                canvas.msRequestFullscreen();
            } else if (canvas.requestFullScreen) {
                canvas.requestFullScreen();
            } else if (canvas.requestFullscreen) {
                canvas.requestFullscreen();
            }
        } else {
            if (document.webkitCancelFullScreen) {
                document.webkitCancelFullScreen();
            } else if (document['mozCancelFullScreen']) {
                document['mozCancelFullScreen']();
            } else if (document.msExitFullscreen) {
                document.msExitFullscreen();
            } else if (document.cancelFullScreen) {
                document.cancelFullScreen();
            } else if (document.exitFullscreen) {
                document.exitFullscreen();
            }
        }
        return true;
    };

    WebGLGraphicsDevice.prototype.createSampler = function (sampler) {
        var samplerKey = sampler.minFilter.toString() + ':' + sampler.magFilter.toString() + ':' + sampler.wrapS.toString() + ':' + sampler.wrapT.toString() + ':' + sampler.wrapR.toString() + ':' + sampler.maxAnisotropy.toString();

        var cachedSamplers = this.cachedSamplers;
        var cachedSampler = cachedSamplers[samplerKey];
        if (!cachedSampler) {
            cachedSamplers[samplerKey] = sampler;
            return sampler;
        }
        return cachedSampler;
    };

    WebGLGraphicsDevice.prototype.unsetIndexBuffer = function (indexBuffer) {
        if (this.activeIndexBuffer === indexBuffer) {
            this.activeIndexBuffer = null;
            var gl = this.gl;
            gl.bindBuffer(gl.ELEMENT_ARRAY_BUFFER, null);
        }
    };

    WebGLGraphicsDevice.prototype.bindVertexBuffer = function (buffer) {
        if (this.bindedVertexBuffer !== buffer) {
            this.bindedVertexBuffer = buffer;
            var gl = this.gl;
            gl.bindBuffer(gl.ARRAY_BUFFER, buffer);

            /* if (debug) {
                this.metrics.vertexBufferChanges += 1;
            } */
        }
    };

    WebGLGraphicsDevice.prototype.unbindVertexBuffer = function (buffer) {
        if (this.bindedVertexBuffer === buffer) {
            this.bindedVertexBuffer = null;
            var gl = this.gl;
            gl.bindBuffer(gl.ARRAY_BUFFER, null);
        }
    };

    WebGLGraphicsDevice.prototype.bindTextureUnit = function (unit, target, texture) {
        var state = this.state;
        var gl = this.gl;

        if (state.activeTextureUnit !== unit) {
            state.activeTextureUnit = unit;
            gl.activeTexture(gl.TEXTURE0 + unit);
        }
        gl.bindTexture(target, texture);
    };

    WebGLGraphicsDevice.prototype.bindTexture = function (target, texture) {
        var state = this.state;
        var gl = this.gl;

        var dummyUnit = (state.maxTextureUnit - 1);
        if (state.activeTextureUnit !== dummyUnit) {
            state.activeTextureUnit = dummyUnit;
            gl.activeTexture(gl.TEXTURE0 + dummyUnit);
        }
        gl.bindTexture(target, texture);
    };

    WebGLGraphicsDevice.prototype.unbindTexture = function (texture) {
        var state = this.state;
        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        for (var u = 0; u < lastMaxTextureUnit; u += 1) {
            var textureUnit = textureUnits[u];
            if (textureUnit.texture === texture) {
                textureUnit.texture = null;
                this.bindTextureUnit(u, textureUnit.target, null);
            }
        }
    };

    WebGLGraphicsDevice.prototype.setSampler = function (sampler, target) {
        if (sampler) {
            var gl = this.gl;

            gl.texParameteri(target, gl.TEXTURE_MIN_FILTER, sampler.minFilter);
            gl.texParameteri(target, gl.TEXTURE_MAG_FILTER, sampler.magFilter);
            gl.texParameteri(target, gl.TEXTURE_WRAP_S, sampler.wrapS);
            gl.texParameteri(target, gl.TEXTURE_WRAP_T, sampler.wrapT);

            if (this.TEXTURE_MAX_ANISOTROPY_EXT) {
                gl.texParameteri(target, this.TEXTURE_MAX_ANISOTROPY_EXT, sampler.maxAnisotropy);
            }
        }
    };

    WebGLGraphicsDevice.prototype.setPass = function (pass) {
        var gl = this.gl;
        var state = this.state;

        // Set renderstates
        var renderStatesSet = pass.statesSet;
        var renderStates = pass.states;
        var numRenderStates = renderStates.length;
        var r, renderState;
        for (r = 0; r < numRenderStates; r += 1) {
            renderState = renderStates[r];
            renderState.set.apply(renderState, renderState.values);
        }

        // Reset previous renderstates
        var renderStatesToReset = state.renderStatesToReset;
        var numRenderStatesToReset = renderStatesToReset.length;
        for (r = 0; r < numRenderStatesToReset; r += 1) {
            renderState = renderStatesToReset[r];
            if (!(renderState.name in renderStatesSet)) {
                renderState.reset();
            }
        }

        // Copy set renderstates to be reset later
        state.renderStatesToReset = renderStates;

        // Reset texture units
        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        var currentMaxTextureUnit = pass.numTextureUnits;
        if (currentMaxTextureUnit < lastMaxTextureUnit) {
            var u = currentMaxTextureUnit;
            do {
                var textureUnit = textureUnits[u];
                if (textureUnit.texture) {
                    textureUnit.texture = null;
                    this.bindTextureUnit(u, textureUnit.target, null);
                }
                u += 1;
            } while(u < lastMaxTextureUnit);
        }
        state.lastMaxTextureUnit = currentMaxTextureUnit;

        var program = pass.glProgram;
        if (state.program !== program) {
            state.program = program;
            gl.useProgram(program);
        }

        if (pass.dirty) {
            pass.updateParametersData(this);
        }
    };

    WebGLGraphicsDevice.prototype.enableClientState = function (mask) {
        var gl = this.gl;

        var oldMask = this.clientStateMask;
        this.clientStateMask = mask;

        var disableMask = (oldMask & (~mask));
        var enableMask = ((~oldMask) & mask);
        var n;

        if (disableMask) {
            if ((disableMask & 0xff) === 0) {
                disableMask >>= 8;
                n = 8;
            } else {
                n = 0;
            }
            do {
                if (0 !== (0x01 & disableMask)) {
                    gl.disableVertexAttribArray(n);
                }
                n += 1;
                disableMask >>= 1;
            } while(disableMask);
        }

        if (enableMask) {
            if ((enableMask & 0xff) === 0) {
                enableMask >>= 8;
                n = 8;
            } else {
                n = 0;
            }
            do {
                if (0 !== (0x01 & enableMask)) {
                    gl.enableVertexAttribArray(n);
                }
                n += 1;
                enableMask >>= 1;
            } while(enableMask);
        }
    };

    WebGLGraphicsDevice.prototype.setTexture = function (textureUnitIndex, texture, sampler) {
        var state = this.state;
        var gl = this.gl;

        var textureUnit = state.textureUnits[textureUnitIndex];
        var oldgltarget = textureUnit.target;
        var oldglobject = textureUnit.texture;

        if (texture) {
            var gltarget = texture.target;
            var globject = texture.glTexture;
            if (oldglobject !== globject || oldgltarget !== gltarget) {
                textureUnit.target = gltarget;
                textureUnit.texture = globject;

                if (state.activeTextureUnit !== textureUnitIndex) {
                    state.activeTextureUnit = textureUnitIndex;
                    gl.activeTexture(gl.TEXTURE0 + textureUnitIndex);
                }

                if (oldgltarget !== gltarget && oldglobject) {
                    gl.bindTexture(oldgltarget, null);
                }

                gl.bindTexture(gltarget, globject);

                if (texture.sampler !== sampler) {
                    texture.sampler = sampler;

                    this.setSampler(sampler, gltarget);
                }

                /* if (debug) {
                    this.metrics.textureChanges += 1;
                } */
            }
        } else {
            if (oldgltarget && oldglobject) {
                textureUnit.target = 0;
                textureUnit.texture = null;

                if (state.activeTextureUnit !== textureUnitIndex) {
                    state.activeTextureUnit = textureUnitIndex;
                    gl.activeTexture(gl.TEXTURE0 + textureUnitIndex);
                }

                gl.bindTexture(oldgltarget, null);
            }
        }
    };

    WebGLGraphicsDevice.prototype.setProgram = function (program) {
        var state = this.state;
        if (state.program !== program) {
            state.program = program;
            this.gl.useProgram(program);
        }
    };

    WebGLGraphicsDevice.prototype.syncState = function () {
        var state = this.state;
        var gl = this.gl;

        if (state.depthTestEnable) {
            gl.enable(gl.DEPTH_TEST);
        } else {
            gl.disable(gl.DEPTH_TEST);
        }

        gl.depthFunc(state.depthFunc);

        gl.depthMask(state.depthMask);

        if (state.blendEnable) {
            gl.enable(gl.BLEND);
        } else {
            gl.disable(gl.BLEND);
        }

        gl.blendFunc(state.blendSrc, state.blendDst);

        if (state.cullFaceEnable) {
            gl.enable(gl.CULL_FACE);
        } else {
            gl.disable(gl.CULL_FACE);
        }

        gl.cullFace(state.cullFace);

        gl.frontFace(state.frontFace);

        var colorMask = state.colorMask;
        gl.colorMask(colorMask[0], colorMask[1], colorMask[2], colorMask[3]);

        if (state.stencilTestEnable) {
            gl.enable(gl.STENCIL_TEST);
        } else {
            gl.disable(gl.STENCIL_TEST);
        }

        gl.stencilFunc(state.stencilFunc, state.stencilRef, state.stencilMask);

        gl.stencilOp(state.stencilFail, state.stencilZFail, state.stencilZPass);

        if (state.polygonOffsetFillEnable) {
            gl.enable(gl.POLYGON_OFFSET_FILL);
        } else {
            gl.disable(gl.POLYGON_OFFSET_FILL);
        }

        gl.polygonOffset(state.polygonOffsetFactor, state.polygonOffsetUnits);

        gl.lineWidth(state.lineWidth);

        gl.activeTexture(gl.TEXTURE0 + state.activeTextureUnit);

        var currentBox = this.state.viewportBox;
        gl.viewport(currentBox[0], currentBox[1], currentBox[2], currentBox[3]);

        currentBox = this.state.scissorBox;
        gl.scissor(currentBox[0], currentBox[1], currentBox[2], currentBox[3]);

        var currentColor = state.clearColor;
        gl.clearColor(currentColor[0], currentColor[1], currentColor[2], currentColor[3]);

        gl.clearDepth(state.clearDepth);

        gl.clearStencil(state.clearStencil);
    };

    WebGLGraphicsDevice.prototype.resetStates = function () {
        var state = this.state;

        var lastMaxTextureUnit = state.lastMaxTextureUnit;
        var textureUnits = state.textureUnits;
        for (var u = 0; u < lastMaxTextureUnit; u += 1) {
            var textureUnit = textureUnits[u];
            if (textureUnit.texture) {
                this.bindTextureUnit(u, textureUnit.target, null);
                textureUnit.texture = null;
                textureUnit.target = 0;
            }
        }
    };

    WebGLGraphicsDevice.prototype.destroy = function () {
        delete this.activeTechnique;
        delete this.activeIndexBuffer;
        delete this.bindedVertexBuffer;

        if (this.immediateVertexBuffer) {
            this.immediateVertexBuffer.destroy();
            delete this.immediateVertexBuffer;
        }

        delete this.gl;

        if (typeof DDSLoader !== 'undefined') {
            DDSLoader.destroy();
        }
    };

    WebGLGraphicsDevice.create = function (canvas, params) {
        var getAvailableContext = function getAvailableContextFn(canvas, params, contextList) {
            if (canvas.getContext) {
                var canvasParams = {
                    alpha: false,
                    depth: true,
                    stencil: true,
                    antialias: false
                };

                var multisample = params.multisample;
                if (multisample !== undefined && 1 < multisample) {
                    canvasParams.antialias = true;
                }

                var alpha = params.alpha;
                if (alpha) {
                    canvasParams.alpha = true;
                }

                if (params.depth === false) {
                    canvasParams.depth = false;
                }

                if (params.stencil === false) {
                    canvasParams.stencil = false;
                }

                var numContexts = contextList.length, i;
                for (i = 0; i < numContexts; i += 1) {
                    try  {
                        var context = canvas.getContext(contextList[i], canvasParams);
                        if (context) {
                            return context;
                        }
                    } catch (ex) {
                    }
                }
            }
            return null;
        };

        // TODO: Test if we can also use "webkit-3d" and "moz-webgl"
        var gl = getAvailableContext(canvas, params, ['webgl', 'experimental-webgl']);
        if (!gl) {
            return null;
        }

        var width = (gl.drawingBufferWidth || canvas.width);
        var height = (gl.drawingBufferHeight || canvas.height);

        gl.enable(gl.SCISSOR_TEST);
        gl.depthRange(0.0, 1.0);
        gl.pixelStorei(gl.UNPACK_ALIGNMENT, 1);

        //gl.hint(gl.GENERATE_MIPMAP_HINT, gl.NICEST);
        var gd = new WebGLGraphicsDevice();
        gd.gl = gl;
        gd.width = width;
        gd.height = height;

        var extensions = gl.getSupportedExtensions();

        var extensionsMap = {};
        var numExtensions = extensions.length;
        var n;
        for (n = 0; n < numExtensions; n += 1) {
            extensionsMap[extensions[n]] = true;
        }

        if (extensions) {
            extensions = extensions.join(' ');
        } else {
            extensions = '';
        }
        gd.extensions = extensions;
        gd.shadingLanguageVersion = gl.getParameter(gl.SHADING_LANGUAGE_VERSION);
        gd.rendererVersion = gl.getParameter(gl.VERSION);
        gd.renderer = gl.getParameter(gl.RENDERER);
        gd.vendor = gl.getParameter(gl.VENDOR);

        if (extensionsMap['WEBGL_compressed_texture_s3tc']) {
            gd.WEBGL_compressed_texture_s3tc = true;
            gd.compressedTexturesExtension = gl.getExtension('WEBGL_compressed_texture_s3tc');
        } else if (extensionsMap['WEBKIT_WEBGL_compressed_texture_s3tc']) {
            gd.WEBGL_compressed_texture_s3tc = true;
            gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_texture_s3tc');
        } else if (extensionsMap['MOZ_WEBGL_compressed_texture_s3tc']) {
            gd.WEBGL_compressed_texture_s3tc = true;
            gd.compressedTexturesExtension = gl.getExtension('MOZ_WEBGL_compressed_texture_s3tc');
        } else if (extensionsMap['WEBKIT_WEBGL_compressed_textures']) {
            gd.compressedTexturesExtension = gl.getExtension('WEBKIT_WEBGL_compressed_textures');
        }

        var anisotropyExtension;
        if (extensionsMap['EXT_texture_filter_anisotropic']) {
            anisotropyExtension = gl.getExtension('EXT_texture_filter_anisotropic');
        } else if (extensionsMap['MOZ_EXT_texture_filter_anisotropic']) {
            anisotropyExtension = gl.getExtension('MOZ_EXT_texture_filter_anisotropic');
        } else if (extensionsMap['WEBKIT_EXT_texture_filter_anisotropic']) {
            anisotropyExtension = gl.getExtension('WEBKIT_EXT_texture_filter_anisotropic');
        }
        if (anisotropyExtension) {
            gd.TEXTURE_MAX_ANISOTROPY_EXT = anisotropyExtension.TEXTURE_MAX_ANISOTROPY_EXT;
            gd.maxAnisotropy = gl.getParameter(anisotropyExtension.MAX_TEXTURE_MAX_ANISOTROPY_EXT);
        } else {
            gd.maxAnisotropy = 1;
        }

        // Enable OES_element_index_uint extension
        gl.getExtension('OES_element_index_uint');

        if (extensionsMap['WEBGL_draw_buffers']) {
            gd.WEBGL_draw_buffers = true;
            gd.drawBuffersExtension = gl.getExtension('WEBGL_draw_buffers');
        } else if (extensionsMap['EXT_draw_buffers']) {
            gd.drawBuffersExtension = gl.getExtension('EXT_draw_buffers');
        }

        gd.PRIMITIVE_POINTS = gl.POINTS;
        gd.PRIMITIVE_LINES = gl.LINES;
        gd.PRIMITIVE_LINE_LOOP = gl.LINE_LOOP;
        gd.PRIMITIVE_LINE_STRIP = gl.LINE_STRIP;
        gd.PRIMITIVE_TRIANGLES = gl.TRIANGLES;
        gd.PRIMITIVE_TRIANGLE_STRIP = gl.TRIANGLE_STRIP;
        gd.PRIMITIVE_TRIANGLE_FAN = gl.TRIANGLE_FAN;

        gd.INDEXFORMAT_UBYTE = gl.UNSIGNED_BYTE;
        gd.INDEXFORMAT_USHORT = gl.UNSIGNED_SHORT;
        gd.INDEXFORMAT_UINT = gl.UNSIGNED_INT;

        // Detect IE11 partial WebGL implementation...
        gd.fixIE = (gd.vendor === 'Microsoft' && -1 !== gd.rendererVersion.indexOf('0.9'));

        var getNormalizationScale = function getNormalizationScaleFn(format) {
            if (format === gl.BYTE) {
                return 0x7f;
            } else if (format === gl.UNSIGNED_BYTE) {
                return 0xff;
            } else if (format === gl.SHORT) {
                return 0x7fff;
            } else if (format === gl.UNSIGNED_SHORT) {
                return 0xffff;
            } else if (format === gl.INT) {
                return 0x7fffffff;
            } else if (format === gl.UNSIGNED_INT) {
                return 0xffffffff;
            } else {
                return 1;
            }
        };

        var makeVertexformat = function makeVertexformatFn(n, c, s, f, name) {
            var attributeFormat = {
                numComponents: c,
                stride: s,
                componentStride: (s / c),
                format: f,
                name: name,
                normalized: undefined,
                normalizationScale: undefined,
                typedSetter: undefined,
                typedArray: undefined
            };
            if (n) {
                attributeFormat.normalized = true;
                attributeFormat.normalizationScale = getNormalizationScale(f);
            } else {
                attributeFormat.normalized = false;
                attributeFormat.normalizationScale = 1;
            }

            if (typeof DataView !== 'undefined' && 'setFloat32' in DataView.prototype) {
                if (f === gl.BYTE) {
                    attributeFormat.typedSetter = DataView.prototype.setInt8;
                } else if (f === gl.UNSIGNED_BYTE) {
                    attributeFormat.typedSetter = DataView.prototype.setUint8;
                } else if (f === gl.SHORT) {
                    attributeFormat.typedSetter = DataView.prototype.setInt16;
                } else if (f === gl.UNSIGNED_SHORT) {
                    attributeFormat.typedSetter = DataView.prototype.setUint16;
                } else if (f === gl.INT) {
                    attributeFormat.typedSetter = DataView.prototype.setInt32;
                } else if (f === gl.UNSIGNED_INT) {
                    attributeFormat.typedSetter = DataView.prototype.setUint32;
                } else {
                    attributeFormat.typedSetter = DataView.prototype.setFloat32;
                }
            } else {
                if (f === gl.BYTE) {
                    attributeFormat.typedArray = Int8Array;
                } else if (f === gl.UNSIGNED_BYTE) {
                    attributeFormat.typedArray = Uint8Array;
                } else if (f === gl.SHORT) {
                    attributeFormat.typedArray = Int16Array;
                } else if (f === gl.UNSIGNED_SHORT) {
                    attributeFormat.typedArray = Uint16Array;
                } else if (f === gl.INT) {
                    attributeFormat.typedArray = Int32Array;
                } else if (f === gl.UNSIGNED_INT) {
                    attributeFormat.typedArray = Uint32Array;
                } else {
                    attributeFormat.typedArray = Float32Array;
                }
            }
            return attributeFormat;
        };

        if (gd.fixIE) {
            gd.VERTEXFORMAT_BYTE4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'BYTE4');
            gd.VERTEXFORMAT_BYTE4N = makeVertexformat(0, 4, 16, gl.FLOAT, 'BYTE4N');
            gd.VERTEXFORMAT_UBYTE4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'UBYTE4');
            gd.VERTEXFORMAT_UBYTE4N = makeVertexformat(0, 4, 16, gl.FLOAT, 'UBYTE4N');
            gd.VERTEXFORMAT_SHORT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'SHORT2');
            gd.VERTEXFORMAT_SHORT2N = makeVertexformat(0, 2, 8, gl.FLOAT, 'SHORT2N');
            gd.VERTEXFORMAT_SHORT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'SHORT4');
            gd.VERTEXFORMAT_SHORT4N = makeVertexformat(0, 4, 16, gl.FLOAT, 'SHORT4N');
            gd.VERTEXFORMAT_USHORT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'USHORT2');
            gd.VERTEXFORMAT_USHORT2N = makeVertexformat(0, 2, 8, gl.FLOAT, 'USHORT2N');
            gd.VERTEXFORMAT_USHORT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'USHORT4');
            gd.VERTEXFORMAT_USHORT4N = makeVertexformat(0, 4, 16, gl.FLOAT, 'USHORT4N');
            gd.VERTEXFORMAT_FLOAT1 = makeVertexformat(0, 1, 4, gl.FLOAT, 'FLOAT1');
            gd.VERTEXFORMAT_FLOAT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'FLOAT2');
            gd.VERTEXFORMAT_FLOAT3 = makeVertexformat(0, 3, 12, gl.FLOAT, 'FLOAT3');
            gd.VERTEXFORMAT_FLOAT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'FLOAT4');
        } else {
            gd.VERTEXFORMAT_BYTE4 = makeVertexformat(0, 4, 4, gl.BYTE, 'BYTE4');
            gd.VERTEXFORMAT_BYTE4N = makeVertexformat(1, 4, 4, gl.BYTE, 'BYTE4N');
            gd.VERTEXFORMAT_UBYTE4 = makeVertexformat(0, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4');
            gd.VERTEXFORMAT_UBYTE4N = makeVertexformat(1, 4, 4, gl.UNSIGNED_BYTE, 'UBYTE4N');
            gd.VERTEXFORMAT_SHORT2 = makeVertexformat(0, 2, 4, gl.SHORT, 'SHORT2');
            gd.VERTEXFORMAT_SHORT2N = makeVertexformat(1, 2, 4, gl.SHORT, 'SHORT2N');
            gd.VERTEXFORMAT_SHORT4 = makeVertexformat(0, 4, 8, gl.SHORT, 'SHORT4');
            gd.VERTEXFORMAT_SHORT4N = makeVertexformat(1, 4, 8, gl.SHORT, 'SHORT4N');
            gd.VERTEXFORMAT_USHORT2 = makeVertexformat(0, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2');
            gd.VERTEXFORMAT_USHORT2N = makeVertexformat(1, 2, 4, gl.UNSIGNED_SHORT, 'USHORT2N');
            gd.VERTEXFORMAT_USHORT4 = makeVertexformat(0, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4');
            gd.VERTEXFORMAT_USHORT4N = makeVertexformat(1, 4, 8, gl.UNSIGNED_SHORT, 'USHORT4N');
            gd.VERTEXFORMAT_FLOAT1 = makeVertexformat(0, 1, 4, gl.FLOAT, 'FLOAT1');
            gd.VERTEXFORMAT_FLOAT2 = makeVertexformat(0, 2, 8, gl.FLOAT, 'FLOAT2');
            gd.VERTEXFORMAT_FLOAT3 = makeVertexformat(0, 3, 12, gl.FLOAT, 'FLOAT3');
            gd.VERTEXFORMAT_FLOAT4 = makeVertexformat(0, 4, 16, gl.FLOAT, 'FLOAT4');
        }

        var maxAttributes = gl.getParameter(gl.MAX_VERTEX_ATTRIBS);
        if (maxAttributes < 16) {
            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR0 = WebGLGraphicsDevice.prototype.SEMANTIC_POSITION = WebGLGraphicsDevice.prototype.SEMANTIC_POSITION0 = 0;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR1 = WebGLGraphicsDevice.prototype.SEMANTIC_BLENDWEIGHT = WebGLGraphicsDevice.prototype.SEMANTIC_BLENDWEIGHT0 = 1;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR2 = WebGLGraphicsDevice.prototype.SEMANTIC_NORMAL = WebGLGraphicsDevice.prototype.SEMANTIC_NORMAL0 = 2;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR3 = WebGLGraphicsDevice.prototype.SEMANTIC_COLOR = WebGLGraphicsDevice.prototype.SEMANTIC_COLOR0 = 3;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR7 = WebGLGraphicsDevice.prototype.SEMANTIC_BLENDINDICES = WebGLGraphicsDevice.prototype.SEMANTIC_BLENDINDICES0 = 4;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR8 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD0 = 5;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR9 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD1 = 6;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR14 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD6 = WebGLGraphicsDevice.prototype.SEMANTIC_TANGENT = WebGLGraphicsDevice.prototype.SEMANTIC_TANGENT0 = 7;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR15 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD7 = WebGLGraphicsDevice.prototype.SEMANTIC_BINORMAL0 = WebGLGraphicsDevice.prototype.SEMANTIC_BINORMAL = 8;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR10 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD2 = 9;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR11 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD3 = 10;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR12 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD4 = 11;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR13 = WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD5 = 12;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR4 = WebGLGraphicsDevice.prototype.SEMANTIC_COLOR1 = WebGLGraphicsDevice.prototype.SEMANTIC_SPECULAR = 13;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR5 = WebGLGraphicsDevice.prototype.SEMANTIC_FOGCOORD = WebGLGraphicsDevice.prototype.SEMANTIC_TESSFACTOR = 14;

            WebGLGraphicsDevice.prototype.SEMANTIC_ATTR6 = WebGLGraphicsDevice.prototype.SEMANTIC_PSIZE = WebGLGraphicsDevice.prototype.SEMANTIC_PSIZE0 = 15;
        }

        gd.DEFAULT_SAMPLER = {
            minFilter: gl.LINEAR_MIPMAP_LINEAR,
            magFilter: gl.LINEAR,
            wrapS: gl.REPEAT,
            wrapT: gl.REPEAT,
            wrapR: gl.REPEAT,
            maxAnisotropy: 1
        };

        gd.cachedSamplers = {};

        var maxTextureUnit = 1;
        var maxUnit = gl.getParameter(gl.MAX_TEXTURE_IMAGE_UNITS);
        if (maxTextureUnit < maxUnit) {
            maxTextureUnit = maxUnit;
        }
        maxUnit = gl.getParameter(gl.MAX_COMBINED_TEXTURE_IMAGE_UNITS);
        if (maxTextureUnit < maxUnit) {
            maxTextureUnit = maxUnit;
        }

        var textureUnits = [];
        textureUnits.length = maxTextureUnit;
        for (var t = 0; t < maxTextureUnit; t += 1) {
            textureUnits[t] = {
                texture: null,
                target: 0
            };
        }

        var defaultDepthFunc = gl.LEQUAL;
        var defaultBlendFuncSrc = gl.SRC_ALPHA;
        var defaultBlendFuncDst = gl.ONE_MINUS_SRC_ALPHA;
        var defaultCullFace = gl.BACK;
        var defaultFrontFace = gl.CCW;
        var defaultStencilFunc = gl.ALWAYS;
        var defaultStencilOp = gl.KEEP;

        var currentState = {
            depthTestEnable: true,
            blendEnable: false,
            cullFaceEnable: true,
            stencilTestEnable: false,
            polygonOffsetFillEnable: false,
            depthMask: true,
            depthFunc: defaultDepthFunc,
            blendSrc: defaultBlendFuncSrc,
            blendDst: defaultBlendFuncDst,
            cullFace: defaultCullFace,
            frontFace: defaultFrontFace,
            colorMask: [true, true, true, true],
            stencilFunc: defaultStencilFunc,
            stencilRef: 0,
            stencilMask: 0xffffffff,
            stencilFail: defaultStencilOp,
            stencilZFail: defaultStencilOp,
            stencilZPass: defaultStencilOp,
            polygonOffsetFactor: 0,
            polygonOffsetUnits: 0,
            lineWidth: 1,
            renderStatesToReset: [],
            viewportBox: [0, 0, width, height],
            scissorBox: [0, 0, width, height],
            clearColor: [0, 0, 0, 1],
            clearDepth: 1.0,
            clearStencil: 0,
            activeTextureUnit: 0,
            maxTextureUnit: maxTextureUnit,
            lastMaxTextureUnit: 0,
            textureUnits: textureUnits,
            program: null
        };
        gd.state = currentState;

        gd.counters = {
            textures: 0,
            vertexBuffers: 0,
            indexBuffers: 0,
            renderTargets: 0,
            renderBuffers: 0,
            shaders: 0,
            techniques: 0
        };

        /* if (debug) {
            gd.metrics = {
                renderTargetChanges: 0,
                textureChanges: 0,
                renderStateChanges: 0,
                vertexBufferChanges: 0,
                indexBufferChanges: 0,
                techniqueChanges: 0,
                drawCalls: 0,
                primitives: 0,
                addPrimitives: function addPrimitivesFn(primitive, count) {
                    this.drawCalls += 1;
                    switch (primitive) {
                        case 0x0000:
                            this.primitives += count;
                            break;
                        case 0x0001:
                            this.primitives += (count >> 1);
                            break;
                        case 0x0002:
                            this.primitives += count;
                            break;
                        case 0x0003:
                            this.primitives += count - 1;
                            break;
                        case 0x0004:
                            this.primitives += (count / 3) | 0;
                            break;
                        case 0x0005:
                            this.primitives += count - 2;
                            break;
                        case 0x0006:
                            this.primitives += count - 2;
                            break;
                    }
                }
            };
        } */

        // State handlers
        function setDepthTestEnable(enable) {
            if (currentState.depthTestEnable !== enable) {
                currentState.depthTestEnable = enable;
                if (enable) {
                    gl.enable(gl.DEPTH_TEST);
                } else {
                    gl.disable(gl.DEPTH_TEST);
                }

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setDepthFunc(func) {
            if (currentState.depthFunc !== func) {
                currentState.depthFunc = func;
                gl.depthFunc(func);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setDepthMask(enable) {
            if (currentState.depthMask !== enable) {
                currentState.depthMask = enable;
                gl.depthMask(enable);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setBlendEnable(enable) {
            if (currentState.blendEnable !== enable) {
                currentState.blendEnable = enable;
                if (enable) {
                    gl.enable(gl.BLEND);
                } else {
                    gl.disable(gl.BLEND);
                }

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setBlendFunc(src, dst) {
            if (currentState.blendSrc !== src || currentState.blendDst !== dst) {
                currentState.blendSrc = src;
                currentState.blendDst = dst;
                gl.blendFunc(src, dst);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setCullFaceEnable(enable) {
            if (currentState.cullFaceEnable !== enable) {
                currentState.cullFaceEnable = enable;
                if (enable) {
                    gl.enable(gl.CULL_FACE);
                } else {
                    gl.disable(gl.CULL_FACE);
                }

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setCullFace(face) {
            if (currentState.cullFace !== face) {
                currentState.cullFace = face;
                gl.cullFace(face);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setFrontFace(face) {
            if (currentState.frontFace !== face) {
                currentState.frontFace = face;
                gl.frontFace(face);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setColorMask(mask0, mask1, mask2, mask3) {
            var colorMask = currentState.colorMask;
            if (colorMask[0] !== mask0 || colorMask[1] !== mask1 || colorMask[2] !== mask2 || colorMask[3] !== mask3) {
                colorMask[0] = mask0;
                colorMask[1] = mask1;
                colorMask[2] = mask2;
                colorMask[3] = mask3;
                gl.colorMask(mask0, mask1, mask2, mask3);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setStencilTestEnable(enable) {
            if (currentState.stencilTestEnable !== enable) {
                currentState.stencilTestEnable = enable;
                if (enable) {
                    gl.enable(gl.STENCIL_TEST);
                } else {
                    gl.disable(gl.STENCIL_TEST);
                }

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setStencilFunc(stencilFunc, stencilRef, stencilMask) {
            if (currentState.stencilFunc !== stencilFunc || currentState.stencilRef !== stencilRef || currentState.stencilMask !== stencilMask) {
                currentState.stencilFunc = stencilFunc;
                currentState.stencilRef = stencilRef;
                currentState.stencilMask = stencilMask;
                gl.stencilFunc(stencilFunc, stencilRef, stencilMask);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setStencilOp(stencilFail, stencilZfail, stencilZpass) {
            if (currentState.stencilFail !== stencilFail || currentState.stencilZFail !== stencilZfail || currentState.stencilZPass !== stencilZpass) {
                currentState.stencilFail = stencilFail;
                currentState.stencilZFail = stencilZfail;
                currentState.stencilZPass = stencilZpass;
                gl.stencilOp(stencilFail, stencilZfail, stencilZpass);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setPolygonOffsetFillEnable(enable) {
            if (currentState.polygonOffsetFillEnable !== enable) {
                currentState.polygonOffsetFillEnable = enable;
                if (enable) {
                    gl.enable(gl.POLYGON_OFFSET_FILL);
                } else {
                    gl.disable(gl.POLYGON_OFFSET_FILL);
                }

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setPolygonOffset(factor, units) {
            if (currentState.polygonOffsetFactor !== factor || currentState.polygonOffsetUnits !== units) {
                currentState.polygonOffsetFactor = factor;
                currentState.polygonOffsetUnits = units;
                gl.polygonOffset(factor, units);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function setLineWidth(lineWidth) {
            if (currentState.lineWidth !== lineWidth) {
                currentState.lineWidth = lineWidth;
                gl.lineWidth(lineWidth);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetDepthTestEnable() {
            if (!currentState.depthTestEnable) {
                currentState.depthTestEnable = true;
                gl.enable(gl.DEPTH_TEST);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetDepthFunc() {
            //setDepthFunc(defaultDepthFunc);
            var func = defaultDepthFunc;
            if (currentState.depthFunc !== func) {
                currentState.depthFunc = func;
                gl.depthFunc(func);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetDepthMask() {
            if (!currentState.depthMask) {
                currentState.depthMask = true;
                gl.depthMask(true);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetBlendEnable() {
            if (currentState.blendEnable) {
                currentState.blendEnable = false;
                gl.disable(gl.BLEND);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetBlendFunc() {
            //setBlendFunc(defaultBlendFuncSrc, defaultBlendFuncDst);
            var src = defaultBlendFuncSrc;
            var dst = defaultBlendFuncDst;
            if (currentState.blendSrc !== src || currentState.blendDst !== dst) {
                currentState.blendSrc = src;
                currentState.blendDst = dst;
                gl.blendFunc(src, dst);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetCullFaceEnable() {
            if (!currentState.cullFaceEnable) {
                currentState.cullFaceEnable = true;
                gl.enable(gl.CULL_FACE);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetCullFace() {
            //setCullFace(defaultCullFace);
            var face = defaultCullFace;
            if (currentState.cullFace !== face) {
                currentState.cullFace = face;
                gl.cullFace(face);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetFrontFace() {
            //setFrontFace(defaultFrontFace);
            var face = defaultFrontFace;
            if (currentState.frontFace !== face) {
                currentState.frontFace = face;
                gl.frontFace(face);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetColorMask() {
            //setColorMask(true, true, true, true);
            var colorMask = currentState.colorMask;
            if (colorMask[0] !== true || colorMask[1] !== true || colorMask[2] !== true || colorMask[3] !== true) {
                colorMask[0] = true;
                colorMask[1] = true;
                colorMask[2] = true;
                colorMask[3] = true;
                gl.colorMask(true, true, true, true);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetStencilTestEnable() {
            if (currentState.stencilTestEnable) {
                currentState.stencilTestEnable = false;
                gl.disable(gl.STENCIL_TEST);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetStencilFunc() {
            //setStencilFunc(defaultStencilFunc, 0, 0xffffffff);
            var stencilFunc = defaultStencilFunc;
            if (currentState.stencilFunc !== stencilFunc || currentState.stencilRef !== 0 || currentState.stencilMask !== 0xffffffff) {
                currentState.stencilFunc = stencilFunc;
                currentState.stencilRef = 0;
                currentState.stencilMask = 0xffffffff;
                gl.stencilFunc(stencilFunc, 0, 0xffffffff);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetStencilOp() {
            //setStencilOp(defaultStencilOp, defaultStencilOp, defaultStencilOp);
            var stencilOp = defaultStencilOp;
            if (currentState.stencilFail !== stencilOp || currentState.stencilZFail !== stencilOp || currentState.stencilZPass !== stencilOp) {
                currentState.stencilFail = stencilOp;
                currentState.stencilZFail = stencilOp;
                currentState.stencilZPass = stencilOp;
                gl.stencilOp(stencilOp, stencilOp, stencilOp);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetPolygonOffsetFillEnable() {
            if (currentState.polygonOffsetFillEnable) {
                currentState.polygonOffsetFillEnable = false;
                gl.disable(gl.POLYGON_OFFSET_FILL);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetPolygonOffset() {
            if (currentState.polygonOffsetFactor !== 0 || currentState.polygonOffsetUnits !== 0) {
                currentState.polygonOffsetFactor = 0;
                currentState.polygonOffsetUnits = 0;
                gl.polygonOffset(0, 0);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function resetLineWidth() {
            if (currentState.lineWidth !== 1) {
                currentState.lineWidth = 1;
                gl.lineWidth(1);

                /* if (debug) {
                    gd.metrics.renderStateChanges += 1;
                } */
            }
        }

        function parseBoolean(state) {
            if (typeof state !== 'boolean') {
                return [(state ? true : false)];
            }
            return [state];
        }

        function parseEnum(state) {
            if (typeof state !== 'number') {
                // TODO
                return null;
            }
            return [state];
        }

        function parseEnum2(state) {
            if (typeof state === 'object') {
                var value0 = state[0], value1 = state[1];
                if (typeof value0 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value1 !== 'number') {
                    // TODO
                    return null;
                }
                return [value0, value1];
            }
            return null;
        }

        function parseEnum3(state) {
            if (typeof state === 'object') {
                var value0 = state[0], value1 = state[1], value2 = state[2];
                if (typeof value0 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value1 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value2 !== 'number') {
                    // TODO
                    return null;
                }
                return [value0, value1, value2];
            }
            return null;
        }

        function parseFloat(state) {
            if (typeof state !== 'number') {
                // TODO
                return null;
            }
            return [state];
        }

        function parseFloat2(state) {
            if (typeof state === 'object') {
                var value0 = state[0], value1 = state[1];
                if (typeof value0 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value1 !== 'number') {
                    // TODO
                    return null;
                }
                return [value0, value1];
            }
            return null;
        }

        function parseColorMask(state) {
            if (typeof state === 'object') {
                var value0 = state[0], value1 = state[1], value2 = state[2], value3 = state[3];
                if (typeof value0 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value1 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value2 !== 'number') {
                    // TODO
                    return null;
                }
                if (typeof value3 !== 'number') {
                    // TODO
                    return null;
                }
                return [value0, value1, value2, value3];
            }
            return null;
        }

        var stateHandlers = {};
        var addStateHandler = function addStateHandlerFn(name, sf, rf, pf, dv) {
            stateHandlers[name] = {
                set: sf,
                reset: rf,
                parse: pf,
                defaultValues: dv
            };
        };
        addStateHandler("DepthTestEnable", setDepthTestEnable, resetDepthTestEnable, parseBoolean, [true]);
        addStateHandler("DepthFunc", setDepthFunc, resetDepthFunc, parseEnum, [defaultDepthFunc]);
        addStateHandler("DepthMask", setDepthMask, resetDepthMask, parseBoolean, [true]);
        addStateHandler("BlendEnable", setBlendEnable, resetBlendEnable, parseBoolean, [false]);
        addStateHandler("BlendFunc", setBlendFunc, resetBlendFunc, parseEnum2, [defaultBlendFuncSrc, defaultBlendFuncDst]);
        addStateHandler("CullFaceEnable", setCullFaceEnable, resetCullFaceEnable, parseBoolean, [true]);
        addStateHandler("CullFace", setCullFace, resetCullFace, parseEnum, [defaultCullFace]);
        addStateHandler("FrontFace", setFrontFace, resetFrontFace, parseEnum, [defaultFrontFace]);
        addStateHandler("ColorMask", setColorMask, resetColorMask, parseColorMask, [true, true, true, true]);
        addStateHandler("StencilTestEnable", setStencilTestEnable, resetStencilTestEnable, parseBoolean, [false]);
        addStateHandler("StencilFunc", setStencilFunc, resetStencilFunc, parseEnum3, [defaultStencilFunc, 0, 0xffffffff]);
        addStateHandler("StencilOp", setStencilOp, resetStencilOp, parseEnum3, [defaultStencilOp, defaultStencilOp, defaultStencilOp]);
        addStateHandler("PolygonOffsetFillEnable", setPolygonOffsetFillEnable, resetPolygonOffsetFillEnable, parseBoolean, [false]);
        addStateHandler("PolygonOffset", setPolygonOffset, resetPolygonOffset, parseFloat2, [0, 0]);
        if (!gd.fixIE) {
            addStateHandler("LineWidth", setLineWidth, resetLineWidth, parseFloat, [1]);
        }
        gd.stateHandlers = stateHandlers;

        gd.syncState();

        gd.videoRam = 0;
        gd.desktopWidth = window.screen.width;
        gd.desktopHeight = window.screen.height;

        if (Object.defineProperty) {
            Object.defineProperty(gd, "fullscreen", {
                get: function getFullscreenFn() {
                    return (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement ? true : false);
                },
                set: function setFullscreenFn(newFullscreen) {
                    gd.requestFullScreen(newFullscreen);
                },
                enumerable: true,
                configurable: false
            });

            gd.checkFullScreen = function dummyCheckFullScreenFn() {
            };
        } else {
            gd.fullscreen = false;
            gd.oldFullscreen = false;
        }

        gd.clientStateMask = 0;
        gd.attributeMask = 0;
        gd.activeTechnique = null;
        gd.activeIndexBuffer = null;
        gd.bindedVertexBuffer = null;
        gd.activeRenderTarget = null;

        gd.immediateVertexBuffer = gd.createVertexBuffer({
            numVertices: (256 * 1024 / 16),
            attributes: ['FLOAT4'],
            dynamic: true,
            'transient': true
        });
        gd.immediatePrimitive = -1;
        gd.immediateSemantics = WebGLSemantics.create(gd, []);

        gd.fps = 0;
        gd.numFrames = 0;
        gd.previousFrameTime = TurbulenzEngine.getTime();

        // Need a temporary elements to test capabilities
        var video = document.createElement('video');
        var supportedVideoExtensions = {};
        if (video) {
            if (video.canPlayType('video/webm')) {
                supportedVideoExtensions.webm = true;
            }
            if (video.canPlayType('video/mp4')) {
                supportedVideoExtensions.mp4 = true;
            }
        }
        gd.supportedVideoExtensions = supportedVideoExtensions;
        video = null;

        return gd;
    };
    WebGLGraphicsDevice.version = 1;
    return WebGLGraphicsDevice;
})();

WebGLGraphicsDevice.prototype.SEMANTIC_POSITION = 0;
WebGLGraphicsDevice.prototype.SEMANTIC_POSITION0 = 0;
WebGLGraphicsDevice.prototype.SEMANTIC_BLENDWEIGHT = 1;
WebGLGraphicsDevice.prototype.SEMANTIC_BLENDWEIGHT0 = 1;
WebGLGraphicsDevice.prototype.SEMANTIC_NORMAL = 2;
WebGLGraphicsDevice.prototype.SEMANTIC_NORMAL0 = 2;
WebGLGraphicsDevice.prototype.SEMANTIC_COLOR = 3;
WebGLGraphicsDevice.prototype.SEMANTIC_COLOR0 = 3;
WebGLGraphicsDevice.prototype.SEMANTIC_COLOR1 = 4;
WebGLGraphicsDevice.prototype.SEMANTIC_SPECULAR = 4;
WebGLGraphicsDevice.prototype.SEMANTIC_FOGCOORD = 5;
WebGLGraphicsDevice.prototype.SEMANTIC_TESSFACTOR = 5;
WebGLGraphicsDevice.prototype.SEMANTIC_PSIZE0 = 6;
WebGLGraphicsDevice.prototype.SEMANTIC_BLENDINDICES = 7;
WebGLGraphicsDevice.prototype.SEMANTIC_BLENDINDICES0 = 7;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD = 8;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD0 = 8;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD1 = 9;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD2 = 10;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD3 = 11;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD4 = 12;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD5 = 13;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD6 = 14;
WebGLGraphicsDevice.prototype.SEMANTIC_TEXCOORD7 = 15;
WebGLGraphicsDevice.prototype.SEMANTIC_TANGENT = 14;
WebGLGraphicsDevice.prototype.SEMANTIC_TANGENT0 = 14;
WebGLGraphicsDevice.prototype.SEMANTIC_BINORMAL0 = 15;
WebGLGraphicsDevice.prototype.SEMANTIC_BINORMAL = 15;
WebGLGraphicsDevice.prototype.SEMANTIC_PSIZE = 6;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR0 = 0;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR1 = 1;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR2 = 2;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR3 = 3;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR4 = 4;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR5 = 5;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR6 = 6;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR7 = 7;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR8 = 8;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR9 = 9;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR10 = 10;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR11 = 11;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR12 = 12;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR13 = 13;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR14 = 14;
WebGLGraphicsDevice.prototype.SEMANTIC_ATTR15 = 15;
WebGLGraphicsDevice.prototype.PIXELFORMAT_A8 = 0;
WebGLGraphicsDevice.prototype.PIXELFORMAT_L8 = 1;
WebGLGraphicsDevice.prototype.PIXELFORMAT_L8A8 = 2;
WebGLGraphicsDevice.prototype.PIXELFORMAT_R5G5B5A1 = 3;
WebGLGraphicsDevice.prototype.PIXELFORMAT_R5G6B5 = 4;
WebGLGraphicsDevice.prototype.PIXELFORMAT_R4G4B4A4 = 5;
WebGLGraphicsDevice.prototype.PIXELFORMAT_R8G8B8A8 = 6;
WebGLGraphicsDevice.prototype.PIXELFORMAT_R8G8B8 = 7;
WebGLGraphicsDevice.prototype.PIXELFORMAT_D24S8 = 8;
WebGLGraphicsDevice.prototype.PIXELFORMAT_D16 = 9;
WebGLGraphicsDevice.prototype.PIXELFORMAT_DXT1 = 10;
WebGLGraphicsDevice.prototype.PIXELFORMAT_DXT3 = 11;
WebGLGraphicsDevice.prototype.PIXELFORMAT_DXT5 = 12;
WebGLGraphicsDevice.prototype.PIXELFORMAT_S8 = 13;

// Copyright (c) 2011-2012 Turbulenz Limited
/*global window*/
/*global Touch: false*/
/*global TouchEvent: false*/
/*global TurbulenzEngine: false*/
//
// WebGLInputDevice
//
var WebGLInputDevice = (function () {
    function WebGLInputDevice() {
    }
    // Public API
    WebGLInputDevice.prototype.update = function () {
        if (!this.isWindowFocused) {
            return;
        }

        this.updateGamePad();
    };

    WebGLInputDevice.prototype.addEventListener = function (eventType, eventListener) {
        var i;
        var length;
        var eventHandlers;

        if (this.handlers.hasOwnProperty(eventType)) {
            eventHandlers = this.handlers[eventType];

            if (eventListener) {
                // Check handler is not already stored
                length = eventHandlers.length;
                for (i = 0; i < length; i += 1) {
                    if (eventHandlers[i] === eventListener) {
                        // Event handler has already been added
                        return;
                    }
                }

                eventHandlers.push(eventListener);
            }
        }
    };

    WebGLInputDevice.prototype.removeEventListener = function (eventType, eventListener) {
        var i;
        var length;
        var eventHandlers;

        if (this.handlers.hasOwnProperty(eventType)) {
            eventHandlers = this.handlers[eventType];

            if (eventListener) {
                length = eventHandlers.length;
                for (i = 0; i < length; i += 1) {
                    if (eventHandlers[i] === eventListener) {
                        eventHandlers.splice(i, 1);
                        break;
                    }
                }
            }
        }
    };

    WebGLInputDevice.prototype.lockMouse = function () {
        if (this.isHovering && this.isWindowFocused) {
            this.isMouseLocked = true;
            this.hideMouse();

            this.requestBrowserLock();

            this.setEventHandlersLock();

            return true;
        } else {
            return false;
        }
    };

    WebGLInputDevice.prototype.unlockMouse = function () {
        if (this.isMouseLocked) {
            this.isMouseLocked = false;
            this.showMouse();

            this.requestBrowserUnlock();

            this.setEventHandlersUnlock();

            if (this.isOutsideEngine) {
                this.isOutsideEngine = false;

                this.isHovering = false;

                this.setEventHandlersMouseLeave();

                // Send mouseout event
                this.sendEventToHandlers(this.handlers.mouseleave);
            }

            // Send mouselocklost event
            this.sendEventToHandlers(this.handlers.mouselocklost);

            return true;
        } else {
            return false;
        }
    };

    WebGLInputDevice.prototype.isLocked = function () {
        return this.isMouseLocked;
    };

    WebGLInputDevice.prototype.hideMouse = function () {
        if (this.isHovering) {
            if (!this.isCursorHidden) {
                this.isCursorHidden = true;
                this.previousCursor = document.body.style.cursor;
                document.body.style.cursor = 'none';
                if (this.webkit) {
                    this.ignoreNextMouseMoves = 2;
                }
            }

            return true;
        } else {
            return false;
        }
    };

    WebGLInputDevice.prototype.showMouse = function () {
        if (this.isCursorHidden && !this.isMouseLocked) {
            this.isCursorHidden = false;
            document.body.style.cursor = this.previousCursor;
            return true;
        } else {
            return false;
        }
    };

    WebGLInputDevice.prototype.isHidden = function () {
        return this.isCursorHidden;
    };

    WebGLInputDevice.prototype.isFocused = function () {
        return this.isWindowFocused;
    };

    // Cannot convert keycodes to unicode in javascript so return empty strings
    WebGLInputDevice.prototype.convertToUnicode = function (keyCodeArray) {
        var keyCodeToUnicode = this.keyCodeToUnicode;
        var result = {};
        var length = keyCodeArray.length;
        var i;
        var keyCode;

        for (i = 0; i < length; i += 1) {
            keyCode = keyCodeArray[i];
            result[keyCode] = keyCodeToUnicode[keyCode] || "";
        }

        return result;
    };

    // Private API
    WebGLInputDevice.prototype.sendEventToHandlers = function (eventHandlers, arg0, arg1, arg2, arg3, arg4, arg5) {
        var i;
        var length = eventHandlers.length;

        if (length) {
            for (i = 0; i < length; i += 1) {
                eventHandlers[i](arg0, arg1, arg2, arg3, arg4, arg5);
            }
        }
    };

    WebGLInputDevice.prototype.sendEventToHandlersASync = function (handlers, a0, a1, a2, a3, a4, a5) {
        var sendEvent = WebGLInputDevice.prototype.sendEventToHandlers;
        TurbulenzEngine.setTimeout(function callSendEventToHandlersFn() {
            sendEvent(handlers, a0, a1, a2, a3, a4, a5);
        }, 0);
    };

    WebGLInputDevice.prototype.updateGamePad = function () {
        var magnitude;
        var normalizedMagnitude;

        var gamepads = (navigator.gamepads || navigator.webkitGamepads || (navigator.getGamepads && navigator.getGamepads()) || (navigator.webkitGetGamepads && navigator.webkitGetGamepads()));

        if (gamepads) {
            var deadZone = this.padAxisDeadZone;
            var maxAxisRange = this.maxAxisRange;
            var sendEvent = this.sendEventToHandlersASync;
            var handlers = this.handlers;
            var padButtons = this.padButtons;
            var padMap = this.padMap;
            var leftThumbX = 0;
            var leftThumbY = 0;
            var rightThumbX = 0;
            var rightThumbY = 0;

            var numGamePads = gamepads.length;
            for (var i = 0; i < numGamePads; i += 1) {
                var gamepad = gamepads[i];
                if (gamepad) {
                    // Update button states
                    var buttons = gamepad.buttons;

                    if (this.padTimestampUpdate < gamepad.timestamp) {
                        this.padTimestampUpdate = gamepad.timestamp;

                        var numButtons = buttons.length;
                        for (var n = 0; n < numButtons; n += 1) {
                            var value = buttons[n];
                            if (typeof value === "object") {
                                value = value.value;
                            }
                            if (padButtons[n] !== value) {
                                padButtons[n] = value;

                                var padCode = padMap[n];
                                if (padCode !== undefined) {
                                    if (value) {
                                        sendEvent(handlers.paddown, padCode);
                                    } else {
                                        sendEvent(handlers.padup, padCode);
                                    }
                                }
                            }
                        }
                    }

                    // Update axes states
                    var axes = gamepad.axes;
                    if (axes.length <= 4) {
                        // Axis 1 & 2
                        var lX = axes[0];
                        var lY = -axes[1];
                        magnitude = ((lX * lX) + (lY * lY));

                        if (magnitude > (deadZone * deadZone)) {
                            magnitude = Math.sqrt(magnitude);

                            // Normalize lX and lY
                            lX = (lX / magnitude);
                            lY = (lY / magnitude);

                            if (magnitude > maxAxisRange) {
                                magnitude = maxAxisRange;
                            }

                            // Adjust magnitude relative to the end of the dead zone
                            magnitude -= deadZone;

                            // Normalize the magnitude
                            normalizedMagnitude = (magnitude / (maxAxisRange - deadZone));

                            leftThumbX = (lX * normalizedMagnitude);
                            leftThumbY = (lY * normalizedMagnitude);
                        }

                        // Axis 3 & 4
                        var rX = axes[2];
                        var rY = -axes[3];
                        magnitude = ((rX * rX) + (rY * rY));

                        if (magnitude > (deadZone * deadZone)) {
                            magnitude = Math.sqrt(magnitude);

                            // Normalize lX and lY
                            rX = (rX / magnitude);
                            rY = (rY / magnitude);

                            if (magnitude > maxAxisRange) {
                                magnitude = maxAxisRange;
                            }

                            // Adjust magnitude relative to the end of the dead zone
                            magnitude -= deadZone;

                            // Normalize the magnitude
                            normalizedMagnitude = (magnitude / (maxAxisRange - deadZone));

                            rightThumbX = (rX * normalizedMagnitude);
                            rightThumbY = (rY * normalizedMagnitude);
                        }

                        sendEvent(handlers.padmove, leftThumbX, leftThumbY, buttons[6], rightThumbX, rightThumbY, buttons[7]);
                    }

                    break;
                }
            }
        }
    };

    // Cannot detect locale in canvas mode
    WebGLInputDevice.prototype.getLocale = function () {
        return "";
    };

    // Returns the local coordinates of the event (i.e. position in
    // Canvas coords)
    WebGLInputDevice.prototype.getCanvasPosition = function (event, position) {
        if (event.offsetX !== undefined) {
            position.x = event.offsetX;
            position.y = event.offsetY;
        } else if (event.layerX !== undefined) {
            position.x = event.layerX;
            position.y = event.layerY;
        }
    };

    // Called when blurring
    WebGLInputDevice.prototype.resetKeyStates = function () {
        var k;
        var pressedKeys = this.pressedKeys;
        var keyUpHandlers = this.handlers.keyup;

        for (k in pressedKeys) {
            if (pressedKeys.hasOwnProperty(k) && pressedKeys[k]) {
                k = parseInt(k, 10);
                pressedKeys[k] = false;
                this.sendEventToHandlers(keyUpHandlers, k);
            }
        }
    };

    // Private mouse event methods
    WebGLInputDevice.prototype.onMouseOver = function (event) {
        var position = {};
        var mouseOverHandlers = this.handlers.mouseover;

        event.stopPropagation();
        event.preventDefault();

        this.getCanvasPosition(event, position);

        this.lastX = event.screenX;
        this.lastY = event.screenY;

        this.sendEventToHandlers(mouseOverHandlers, position.x, position.y);
    };

    WebGLInputDevice.prototype.onMouseMove = function (event) {
        var mouseMoveHandlers = this.handlers.mousemove;

        var deltaX, deltaY;

        event.stopPropagation();
        event.preventDefault();

        if (this.ignoreNextMouseMoves) {
            this.ignoreNextMouseMoves -= 1;
            return;
        }

        if (event.movementX !== undefined) {
            deltaX = event.movementX;
            deltaY = event.movementY;
        } else if (event.mozMovementX !== undefined) {
            deltaX = event.mozMovementX;
            deltaY = event.mozMovementY;
        } else if (event.webkitMovementX !== undefined) {
            deltaX = event.webkitMovementX;
            deltaY = event.webkitMovementY;
        } else {
            deltaX = (event.screenX - this.lastX);
            deltaY = (event.screenY - this.lastY);
            if (0 === deltaX && 0 === deltaY) {
                return;
            }
        }

        this.lastX = event.screenX;
        this.lastY = event.screenY;

        this.sendEventToHandlers(mouseMoveHandlers, deltaX, deltaY);
    };

    WebGLInputDevice.prototype.onWheel = function (event) {
        var mouseWheelHandlers = this.handlers.mousewheel;

        var scrollDelta;

        event.stopPropagation();
        event.preventDefault();

        if (event.wheelDelta) {
            if (window.opera) {
                scrollDelta = event.wheelDelta < 0 ? 1 : -1;
            } else {
                scrollDelta = event.wheelDelta > 0 ? 1 : -1;
            }
        } else {
            scrollDelta = event.detail < 0 ? 1 : -1;
        }

        this.sendEventToHandlers(mouseWheelHandlers, scrollDelta);
    };

    WebGLInputDevice.prototype.emptyEvent = function (event) {
        event.stopPropagation();
        event.preventDefault();
    };

    WebGLInputDevice.prototype.onWindowFocus = function () {
        if (this.isHovering && window.document.activeElement === this.canvas) {
            this.addInternalEventListener(window, 'mousedown', this.onMouseDown);
        }
    };

    WebGLInputDevice.prototype.onFocus = function () {
        var canvas = this.canvas;
        var handlers = this.handlers;
        var focusHandlers = handlers.focus;

        if (!this.isWindowFocused) {
            this.isWindowFocused = true;

            window.focus();
            canvas.focus();

            this.setEventHandlersFocus();

            canvas.oncontextmenu = function () {
                return false;
            };

            this.sendEventToHandlers(focusHandlers);
        }
    };

    WebGLInputDevice.prototype.onBlur = function () {
        if (this.ignoreNextBlur) {
            this.ignoreNextBlur = false;
            return;
        }

        var canvas = this.canvas;
        var handlers = this.handlers;
        var blurHandlers = handlers.blur;

        if (this.isMouseLocked) {
            this.unlockMouse();
        }

        if (this.isWindowFocused) {
            this.isWindowFocused = false;

            this.resetKeyStates();
            this.setEventHandlersBlur();
            canvas.oncontextmenu = null;

            this.sendEventToHandlers(blurHandlers);
        }
    };

    WebGLInputDevice.prototype.onMouseDown = function (event) {
        var handlers = this.handlers;

        if (this.isHovering) {
            var mouseDownHandlers = handlers.mousedown;
            var button = event.button;
            var position = {};

            this.onFocus();

            event.stopPropagation();
            event.preventDefault();

            if (button < 3) {
                button = this.mouseMap[button];
            }

            this.getCanvasPosition(event, position);

            this.sendEventToHandlers(mouseDownHandlers, button, position.x, position.y);
        } else {
            this.onBlur();
        }
    };

    WebGLInputDevice.prototype.onMouseUp = function (event) {
        var mouseUpHandlers = this.handlers.mouseup;

        if (this.isHovering) {
            var button = event.button;
            var position = {};

            event.stopPropagation();
            event.preventDefault();

            if (button < 3) {
                button = this.mouseMap[button];
            }

            this.getCanvasPosition(event, position);

            this.sendEventToHandlers(mouseUpHandlers, button, position.x, position.y);
        }
    };

    // Private key event methods
    WebGLInputDevice.prototype.onKeyDown = function (event) {
        var keyDownHandlers = this.handlers.keydown;
        var pressedKeys = this.pressedKeys;
        var keyCodes = this.keyCodes;

        event.stopPropagation();
        event.preventDefault();

        var keyCode = event.keyCode;
        keyCode = this.keyMap[keyCode];

        if (undefined !== keyCode && (keyCodes.ESCAPE !== keyCode)) {
            // Handle left / right key locations
            //   DOM_KEY_LOCATION_STANDARD = 0x00;
            //   DOM_KEY_LOCATION_LEFT     = 0x01;
            //   DOM_KEY_LOCATION_RIGHT    = 0x02;
            //   DOM_KEY_LOCATION_NUMPAD   = 0x03;
            //   DOM_KEY_LOCATION_MOBILE   = 0x04;
            //   DOM_KEY_LOCATION_JOYSTICK = 0x05;
            var keyLocation = (typeof event.location === "number" ? event.location : event.keyLocation);
            if (2 === keyLocation) {
                // The Turbulenz KeyCodes are such that CTRL, SHIFT
                // and ALT have their RIGHT versions exactly one above
                // the LEFT versions.
                keyCode = keyCode + 1;
            }
            if (!pressedKeys[keyCode]) {
                pressedKeys[keyCode] = true;
                this.sendEventToHandlers(keyDownHandlers, keyCode);
            }
        }
    };

    WebGLInputDevice.prototype.onKeyUp = function (event) {
        var keyUpHandlers = this.handlers.keyup;
        var pressedKeys = this.pressedKeys;
        var keyCodes = this.keyCodes;

        event.stopPropagation();
        event.preventDefault();

        var keyCode = event.keyCode;
        keyCode = this.keyMap[keyCode];

        if (keyCode === keyCodes.ESCAPE) {
            this.unlockMouse();

            if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {
                if (document.webkitCancelFullScreen) {
                    document.webkitCancelFullScreen();
                } else if (document.cancelFullScreen) {
                    document.cancelFullScreen();
                } else if (document['mozCancelFullScreen']) {
                    document['mozCancelFullScreen']();
                } else if (document.msExitFullscreen) {
                    document.msExitFullscreen();
                } else if (document.exitFullscreen) {
                    document.exitFullscreen();
                }
            }
        } else if (undefined !== keyCode) {
            // Handle LEFT / RIGHT.  (See OnKeyDown)
            var keyLocation = (typeof event.location === "number" ? event.location : event.keyLocation);
            if (2 === keyLocation) {
                keyCode = keyCode + 1;
            }
            if (pressedKeys[keyCode]) {
                pressedKeys[keyCode] = false;
                this.sendEventToHandlers(keyUpHandlers, keyCode);

                if ((627 === keyCode || 628 === keyCode) && (this.macosx)) {
                    this.resetKeyStates();
                }
            }
        }
    };

    // Private touch event methods
    WebGLInputDevice.prototype.onTouchStart = function (event) {
        var eventHandlers = this.handlers.touchstart;

        event.preventDefault();

        // Store new touches
        this.addTouches(event.changedTouches);

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.onTouchEnd = function (event) {
        var eventHandlers = this.handlers.touchend;

        event.preventDefault();

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        // Remove ended touches
        this.removeTouches(event.changedTouches);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.onTouchMove = function (event) {
        var eventHandlers = this.handlers.touchmove;

        event.preventDefault();

        this.addTouches(event.changedTouches);

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.onTouchEnter = function (event) {
        var eventHandlers = this.handlers.touchenter;

        event.preventDefault();

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.onTouchLeave = function (event) {
        var eventHandlers = this.handlers.touchleave;

        event.preventDefault();

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.onTouchCancel = function (event) {
        var eventHandlers = this.handlers.touchcancel;

        event.preventDefault();

        event = this.convertW3TouchEventToTurbulenzTouchEvent(event);

        // Remove canceled touches
        this.removeTouches(event.changedTouches);

        this.sendEventToHandlers(eventHandlers, event);
    };

    WebGLInputDevice.prototype.convertW3TouchEventToTurbulenzTouchEvent = function (w3TouchEvent) {
        // Initialize changedTouches
        var changedTouches = this.convertW3TouchListToTurbulenzTouchList(w3TouchEvent.changedTouches);

        // Initialize gameTouches
        var gameTouches = this.convertW3TouchListToTurbulenzTouchList(w3TouchEvent.targetTouches);

        // Initialize touches
        var touches = this.convertW3TouchListToTurbulenzTouchList(w3TouchEvent.touches);

        var touchEventParams = {
            changedTouches: changedTouches,
            gameTouches: gameTouches,
            touches: touches
        };

        return WebGLTouchEvent.create(touchEventParams);
    };

    WebGLInputDevice.prototype.convertW3TouchListToTurbulenzTouchList = function (w3TouchList) {
        // Set changedTouches
        var w3TouchListLength = w3TouchList.length;
        var touchList = [];

        var touch;
        var touchIndex;

        touchList.length = w3TouchListLength;

        for (touchIndex = 0; touchIndex < w3TouchListLength; touchIndex += 1) {
            touch = this.getTouchById(w3TouchList[touchIndex].identifier);
            touchList[touchIndex] = touch;
        }

        return touchList;
    };

    WebGLInputDevice.prototype.convertW3TouchToTurbulenzTouch = function (w3Touch) {
        var canvasElement = this.canvas;
        var canvasRect = canvasElement.getBoundingClientRect();

        var touchParams = {
            force: (w3Touch.force || w3Touch.webkitForce || 0),
            identifier: w3Touch.identifier,
            isGameTouch: (w3Touch.target === canvasElement),
            positionX: (w3Touch.pageX - canvasRect.left),
            positionY: (w3Touch.pageY - canvasRect.top),
            radiusX: (w3Touch.radiusX || w3Touch.webkitRadiusX || 1),
            radiusY: (w3Touch.radiusY || w3Touch.webkitRadiusY || 1),
            rotationAngle: (w3Touch.rotationAngle || w3Touch.webkitRotationAngle || 0)
        };

        return Touch.create(touchParams);
    };

    WebGLInputDevice.prototype.addTouches = function (w3TouchList) {
        var w3TouchListLength = w3TouchList.length;

        var touchIndex;
        var touch;

        for (touchIndex = 0; touchIndex < w3TouchListLength; touchIndex += 1) {
            touch = this.convertW3TouchToTurbulenzTouch(w3TouchList[touchIndex]);
            this.addTouch(touch);
        }
    };

    WebGLInputDevice.prototype.removeTouches = function (w3TouchList) {
        var w3TouchListLength = w3TouchList.length;

        var touchIndex;
        var touchId;

        for (touchIndex = 0; touchIndex < w3TouchListLength; touchIndex += 1) {
            touchId = w3TouchList[touchIndex].identifier;
            this.removeTouchById(touchId);
        }
    };

    WebGLInputDevice.prototype.addTouch = function (touch) {
        this.touches[touch.identifier] = touch;
    };

    WebGLInputDevice.prototype.getTouchById = function (id) {
        return this.touches[id];
    };

    WebGLInputDevice.prototype.removeTouchById = function (id) {
        delete this.touches[id];
    };

    // Canvas event handlers
    WebGLInputDevice.prototype.canvasOnMouseOver = function (event) {
        var mouseEnterHandlers = this.handlers.mouseenter;

        if (!this.isMouseLocked) {
            this.isHovering = true;

            this.lastX = event.screenX;
            this.lastY = event.screenY;

            this.setEventHandlersMouseEnter();

            // Send mouseover event
            this.sendEventToHandlers(mouseEnterHandlers);
        } else {
            this.isOutsideEngine = false;
        }
    };

    WebGLInputDevice.prototype.canvasOnMouseOut = function (/* event */ ) {
        var mouseLeaveHandlers = this.handlers.mouseleave;

        if (!this.isMouseLocked) {
            this.isHovering = false;

            if (this.isCursorHidden) {
                this.showMouse();
            }

            this.setEventHandlersMouseLeave();

            // Send mouseout event
            this.sendEventToHandlers(mouseLeaveHandlers);
        } else {
            this.isOutsideEngine = true;
        }
    };

    // This is required in order to detect hovering when we missed the
    // initial mouseover event
    WebGLInputDevice.prototype.canvasOnMouseDown = function (event) {
        var mouseEnterHandlers = this.handlers.mouseenter;

        this.canvas.onmousedown = null;

        if (!this.isHovering) {
            this.isHovering = true;

            this.lastX = event.screenX;
            this.lastY = event.screenY;

            this.setEventHandlersMouseEnter();

            this.sendEventToHandlers(mouseEnterHandlers);

            this.onMouseDown(event);
        }

        return false;
    };

    // Window event handlers
    WebGLInputDevice.prototype.onFullscreenChanged = function (/* event */ ) {
        if (this.isMouseLocked) {
            if (document.fullscreenElement || document.webkitFullscreenElement || document.mozFullScreenElement || document.msFullscreenElement) {
                this.ignoreNextMouseMoves = 2;
                this.requestBrowserLock();
            } else {
                // Browsers capture the escape key whilst in fullscreen
                this.unlockMouse();
            }
        }
    };

    // Set event handler methods
    WebGLInputDevice.prototype.setEventHandlersMouseEnter = function () {
        if (!this.isFocused()) {
            this.addInternalEventListener(window, 'mousedown', this.onMouseDown);
        }

        this.addInternalEventListener(window, 'mouseup', this.onMouseUp);
        this.addInternalEventListener(window, 'mousemove', this.onMouseOver);
        this.addInternalEventListener(window, 'DOMMouseScroll', this.onWheel);
        this.addInternalEventListener(window, 'mousewheel', this.onWheel);
        this.addInternalEventListener(window, 'click', this.emptyEvent);
    };

    WebGLInputDevice.prototype.setEventHandlersMouseLeave = function () {
        if (!this.isFocused()) {
            this.removeInternalEventListener(window, 'mousedown', this.onMouseDown);
        }

        // Remove mouse event listeners
        this.removeInternalEventListener(window, 'mouseup', this.onMouseUp);
        this.removeInternalEventListener(window, 'mousemove', this.onMouseOver);
        this.removeInternalEventListener(window, 'DOMMouseScroll', this.onWheel);
        this.removeInternalEventListener(window, 'mousewheel', this.onWheel);
        this.removeInternalEventListener(window, 'click', this.emptyEvent);
    };

    WebGLInputDevice.prototype.setEventHandlersFocus = function () {
        this.addInternalEventListener(window, 'keydown', this.onKeyDown);
        this.addInternalEventListener(window, 'keyup', this.onKeyUp);
    };

    WebGLInputDevice.prototype.setEventHandlersBlur = function () {
        this.removeInternalEventListener(window, 'keydown', this.onKeyDown);
        this.removeInternalEventListener(window, 'keyup', this.onKeyUp);
        this.removeInternalEventListener(window, 'mousedown', this.onMouseDown);
    };

    WebGLInputDevice.prototype.setEventHandlersLock = function () {
        this.removeInternalEventListener(window, 'mousemove', this.onMouseOver);

        this.addInternalEventListener(window, 'mousemove', this.onMouseMove);

        this.addInternalEventListener(document, 'fullscreenchange', this.onFullscreenChanged);
        this.addInternalEventListener(document, 'mozfullscreenchange', this.onFullscreenChanged);
        this.addInternalEventListener(document, 'webkitfullscreenchange', this.onFullscreenChanged);
        this.addInternalEventListener(document, 'MSFullscreenChange', this.onFullscreenChanged);
    };

    WebGLInputDevice.prototype.setEventHandlersUnlock = function () {
        this.removeInternalEventListener(document, 'webkitfullscreenchange', this.onFullscreenChanged);
        this.removeInternalEventListener(document, 'mozfullscreenchange', this.onFullscreenChanged);
        this.removeInternalEventListener(document, 'MSFullscreenChange', this.onFullscreenChanged);

        this.removeInternalEventListener(window, 'mousemove', this.onMouseMove);

        this.addInternalEventListener(window, 'mousemove', this.onMouseOver);
    };

    WebGLInputDevice.prototype.setEventHandlersCanvas = function () {
        var canvas = this.canvas;

        this.addInternalEventListener(canvas, 'mouseover', this.canvasOnMouseOver);
        this.addInternalEventListener(canvas, 'mouseout', this.canvasOnMouseOut);
        this.addInternalEventListener(canvas, 'mousedown', this.canvasOnMouseDown);
    };

    WebGLInputDevice.prototype.setEventHandlersWindow = function () {
        this.addInternalEventListener(window, 'blur', this.onBlur);
        this.addInternalEventListener(window, 'focus', this.onWindowFocus);
    };

    WebGLInputDevice.prototype.removeEventHandlersWindow = function () {
        this.removeInternalEventListener(window, 'blur', this.onBlur);
        this.removeInternalEventListener(window, 'focus', this.onWindowFocus);
    };

    WebGLInputDevice.prototype.setEventHandlersTouch = function () {
        var canvas = this.canvas;

        this.addInternalEventListener(canvas, 'touchstart', this.onTouchStart);
        this.addInternalEventListener(canvas, 'touchend', this.onTouchEnd);
        this.addInternalEventListener(canvas, 'touchenter', this.onTouchEnter);
        this.addInternalEventListener(canvas, 'touchleave', this.onTouchLeave);
        this.addInternalEventListener(canvas, 'touchmove', this.onTouchMove);
        this.addInternalEventListener(canvas, 'touchcancel', this.onTouchCancel);
    };

    // Helper methods
    WebGLInputDevice.prototype.addInternalEventListener = function (element, eventName, eventHandler) {
        var elementEventFlag = this.elementEventFlags[element];
        if (!elementEventFlag) {
            this.elementEventFlags[element] = elementEventFlag = {};
        }

        if (!elementEventFlag[eventName]) {
            elementEventFlag[eventName] = true;

            var boundEventHandler = this.boundFunctions[eventHandler];
            if (!boundEventHandler) {
                this.boundFunctions[eventHandler] = boundEventHandler = eventHandler.bind(this);
            }

            element.addEventListener(eventName, boundEventHandler, false);
        }
    };

    WebGLInputDevice.prototype.removeInternalEventListener = function (element, eventName, eventHandler) {
        var elementEventFlag = this.elementEventFlags[element];
        if (elementEventFlag) {
            if (elementEventFlag[eventName]) {
                elementEventFlag[eventName] = false;

                var boundEventHandler = this.boundFunctions[eventHandler];

                element.removeEventListener(eventName, boundEventHandler, false);
            }
        }
    };

    WebGLInputDevice.prototype.destroy = function () {
        if (this.isLocked()) {
            this.setEventHandlersUnlock();
        }

        if (this.isHovering) {
            this.setEventHandlersMouseLeave();
        }

        if (this.isWindowFocused) {
            this.setEventHandlersBlur();
        }

        this.removeEventHandlersWindow();

        var canvas = this.canvas;
        canvas.onmouseover = null;
        canvas.onmouseout = null;
        canvas.onmousedown = null;
    };

    WebGLInputDevice.prototype.isSupported = function (name) {
        var canvas = this.canvas;

        if ((canvas) && (name === "POINTER_LOCK")) {
            var havePointerLock = ('pointerLockElement' in document) || ('mozPointerLockElement' in document) || ('webkitPointerLockElement' in document);

            var requestPointerLock = (canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock);

            if (havePointerLock && requestPointerLock) {
                return true;
            }
        }

        return false;
    };

    WebGLInputDevice.create = function (canvas/*, params: any */ ) {
        var id = new WebGLInputDevice();

        id.lastX = 0;
        id.lastY = 0;

        id.touches = {};

        id.boundFunctions = {};
        id.elementEventFlags = {};

        id.canvas = canvas;
        id.isMouseLocked = false;
        id.isHovering = false;
        id.isWindowFocused = false;
        id.isCursorHidden = false;
        id.isOutsideEngine = false;
        id.previousCursor = '';
        id.ignoreNextMouseMoves = 0;
        id.ignoreNextBlur = false;

        // Used to screen out auto-repeats, dictionary from keycode to boolean,
        // true for each key currently pressed down
        id.pressedKeys = {};

        // Game event handlers
        id.handlers = {
            keydown: [],
            keyup: [],
            mousedown: [],
            mouseup: [],
            mousewheel: [],
            mouseover: [],
            mousemove: [],
            paddown: [],
            padup: [],
            padmove: [],
            mouseenter: [],
            mouseleave: [],
            focus: [],
            blur: [],
            mouselocklost: [],
            touchstart: [],
            touchend: [],
            touchenter: [],
            touchleave: [],
            touchmove: [],
            touchcancel: []
        };

        // Populate the keyCodeToUnicodeTable.  Just use the 'key' part of
        // the keycodes, overriding some special cases.
        var keyCodeToUnicodeTable = {};
        var keyCodes = id.keyCodes;
        for (var k in keyCodes) {
            if (keyCodes.hasOwnProperty(k)) {
                var code = keyCodes[k];
                keyCodeToUnicodeTable[code] = k;
            }
        }
        keyCodeToUnicodeTable[keyCodes.SPACE] = ' ';
        keyCodeToUnicodeTable[keyCodes.NUMBER_0] = '0';
        keyCodeToUnicodeTable[keyCodes.NUMBER_1] = '1';
        keyCodeToUnicodeTable[keyCodes.NUMBER_2] = '2';
        keyCodeToUnicodeTable[keyCodes.NUMBER_3] = '3';
        keyCodeToUnicodeTable[keyCodes.NUMBER_4] = '4';
        keyCodeToUnicodeTable[keyCodes.NUMBER_5] = '5';
        keyCodeToUnicodeTable[keyCodes.NUMBER_6] = '6';
        keyCodeToUnicodeTable[keyCodes.NUMBER_7] = '7';
        keyCodeToUnicodeTable[keyCodes.NUMBER_8] = '8';
        keyCodeToUnicodeTable[keyCodes.NUMBER_9] = '9';
        keyCodeToUnicodeTable[keyCodes.GRAVE] = '`';
        keyCodeToUnicodeTable[keyCodes.MINUS] = '-';
        keyCodeToUnicodeTable[keyCodes.EQUALS] = '=';
        keyCodeToUnicodeTable[keyCodes.LEFT_BRACKET] = '[';
        keyCodeToUnicodeTable[keyCodes.RIGHT_BRACKET] = ']';
        keyCodeToUnicodeTable[keyCodes.SEMI_COLON] = ';';
        keyCodeToUnicodeTable[keyCodes.APOSTROPHE] = "'";
        keyCodeToUnicodeTable[keyCodes.COMMA] = ',';
        keyCodeToUnicodeTable[keyCodes.PERIOD] = '.';
        keyCodeToUnicodeTable[keyCodes.SLASH] = '/';
        keyCodeToUnicodeTable[keyCodes.BACKSLASH] = '\\';

        // KeyMap: Maps JavaScript keycodes to Turbulenz keycodes - some
        // keycodes are consistent across all browsers and some mappings
        // are browser specific.
        var keyMap = {};

        // A-Z
        keyMap[65] = 0;
        keyMap[66] = 1;
        keyMap[67] = 2;
        keyMap[68] = 3;
        keyMap[69] = 4;
        keyMap[70] = 5;
        keyMap[71] = 6;
        keyMap[72] = 7;
        keyMap[73] = 8;
        keyMap[74] = 9;
        keyMap[75] = 10;
        keyMap[76] = 11;
        keyMap[77] = 12;
        keyMap[78] = 13;
        keyMap[79] = 14;
        keyMap[80] = 15;
        keyMap[81] = 16;
        keyMap[82] = 17;
        keyMap[83] = 18;
        keyMap[84] = 19;
        keyMap[85] = 20;
        keyMap[86] = 21;
        keyMap[87] = 22;
        keyMap[88] = 23;
        keyMap[89] = 24;
        keyMap[90] = 25;

        // 0-9
        keyMap[48] = 100;
        keyMap[49] = 101;
        keyMap[50] = 102;
        keyMap[51] = 103;
        keyMap[52] = 104;
        keyMap[53] = 105;
        keyMap[54] = 106;
        keyMap[55] = 107;
        keyMap[56] = 108;
        keyMap[57] = 109;

        // Arrow keys
        keyMap[37] = 200;
        keyMap[39] = 201;
        keyMap[38] = 202;
        keyMap[40] = 203;

        // Modifier keys
        keyMap[16] = 300;

        //keyMap[16] = 301; // RIGHT_SHIFT
        keyMap[17] = 302;

        //keyMap[17] = 303; // RIGHT_CONTROL
        keyMap[18] = 304;
        keyMap[0] = 305;

        // Special keys
        keyMap[27] = 400;
        keyMap[9] = 401;
        keyMap[32] = 402;
        keyMap[8] = 403;
        keyMap[13] = 404;

        // Punctuation keys
        keyMap[223] = 500;
        keyMap[173] = 501;
        keyMap[189] = 501;
        keyMap[61] = 502;
        keyMap[187] = 502;
        keyMap[219] = 503;
        keyMap[221] = 504;
        keyMap[59] = 505;
        keyMap[186] = 505;
        keyMap[192] = 500;
        keyMap[188] = 507;
        keyMap[190] = 508;
        keyMap[222] = 506;

        if (navigator.appVersion.indexOf("Mac") !== -1) {
            keyMap[0] = 500;
        }

        // Non-standard keys
        keyMap[112] = 600;
        keyMap[113] = 601;
        keyMap[114] = 602;
        keyMap[115] = 603;
        keyMap[116] = 604;
        keyMap[117] = 605;
        keyMap[118] = 606;
        keyMap[119] = 607;
        keyMap[120] = 608;
        keyMap[121] = 609;
        keyMap[122] = 610;
        keyMap[123] = 611;

        //keyMap[45 : 612, // NUMPAD_0 (numlock on/off)
        keyMap[96] = 612;

        //keyMap[35] = 613;, // NUMPAD_1 (numlock on/off)
        keyMap[97] = 613;

        //keyMap[40] = 614; // NUMPAD_2 (numlock on/off)
        keyMap[98] = 614;

        //keyMap[34] = 615; // NUMPAD_3 (numlock on/off)
        keyMap[99] = 615;

        //keyMap[37] = 616;, // NUMPAD_4 (numlock on/off)
        keyMap[100] = 616;
        keyMap[12] = 617;
        keyMap[101] = 617;
        keyMap[144] = 617;

        //keyMap[39] = 618; // NUMPAD_6 (numlock on/off)
        keyMap[102] = 618;

        //keyMap[36] = 619; // NUMPAD_7 (numlock on/off)
        keyMap[103] = 619;

        //keyMap[38] = 620; // NUMPAD_8 (numlock on/off)
        keyMap[104] = 620;

        //keyMap[33] = 621; // NUMPAD_9 (numlock on/off)
        keyMap[105] = 621;

        //keyMap[13] = 622; // NUMPAD_ENTER (numlock on/off)
        keyMap[111] = 623;
        keyMap[191] = 623;
        keyMap[106] = 624;
        keyMap[107] = 625;
        keyMap[109] = 626;
        keyMap[91] = 627;
        keyMap[224] = 627;
        keyMap[92] = 628;
        keyMap[93] = 628;

        //: 629, // LEFT_OPTION
        //: 630, // RIGHT_OPTION
        keyMap[20] = 631;
        keyMap[45] = 632;
        keyMap[46] = 633;
        keyMap[36] = 634;
        keyMap[35] = 635;
        keyMap[33] = 636;
        keyMap[34] = 637;

        id.keyMap = keyMap;

        // MouseMap: Maps current mouse controls to new controls
        var mouseMap = {
            0: 0,
            1: 2,
            2: 1
        };

        id.mouseMap = mouseMap;

        // padMap: Maps current pad buttons to new buttons
        var padMap = {
            0: 4,
            1: 5,
            2: 6,
            3: 7,
            4: 10,
            5: 11,
            8: 19,
            9: 18,
            10: 12,
            11: 15,
            12: 0,
            13: 2,
            14: 1,
            15: 3
        };

        id.padMap = padMap;

        id.keyCodeToUnicode = keyCodeToUnicodeTable;

        id.padButtons = [0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0];
        id.padMap = padMap;
        id.padAxisDeadZone = 0.26;
        id.maxAxisRange = 1.0;
        id.padTimestampUpdate = 0;

        // Pointer locking
        var requestPointerLock = (canvas.requestPointerLock || canvas.mozRequestPointerLock || canvas.webkitRequestPointerLock);
        if (requestPointerLock) {
            var exitPointerLock = (document.exitPointerLock || document.mozExitPointerLock || document.webkitExitPointerLock);

            id.onPointerLockChanged = function onPointerLockChangedFn(/* event */ ) {
                var pointerLockElement = (document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement);
                if (pointerLockElement !== id.canvas) {
                    id.unlockMouse();
                }
            };

            id.onPointerLockError = function onPointerLockErrorFn(/* event */ ) {
                id.unlockMouse();
            };

            if ('mozPointerLockElement' in document) {
                id.setEventHandlersPointerLock = function setEventHandlersPointerLockFn() {
                    // firefox changes focus when requesting lock...
                    this.ignoreNextBlur = true;
                    document.addEventListener('mozpointerlockchange', this.onPointerLockChanged, false);
                    document.addEventListener('mozpointerlockerror', this.onPointerLockError, false);
                };

                id.setEventHandlersPointerUnlock = function setEventHandlersPointerUnlockFn() {
                    this.ignoreNextBlur = false;
                    document.removeEventListener('mozpointerlockchange', this.onPointerLockChanged, false);
                    document.removeEventListener('mozpointerlockerror', this.onPointerLockError, false);
                };
            } else if ('webkitPointerLockElement' in document) {
                id.setEventHandlersPointerLock = function setEventHandlersPointerLockFn() {
                    document.addEventListener('webkitpointerlockchange', this.onPointerLockChanged, false);
                    document.addEventListener('webkitpointerlockerror', this.onPointerLockError, false);
                };

                id.setEventHandlersPointerUnlock = function setEventHandlersPointerUnlockFn() {
                    document.removeEventListener('webkitpointerlockchange', this.onPointerLockChanged, false);
                    document.removeEventListener('webkitpointerlockerror', this.onPointerLockError, false);
                };
            } else if ('pointerLockElement' in document) {
                id.setEventHandlersPointerLock = function setEventHandlersPointerLockFn() {
                    document.addEventListener('pointerlockchange', this.onPointerLockChanged, false);
                    document.addEventListener('pointerlockerror', this.onPointerLockError, false);
                };

                id.setEventHandlersPointerUnlock = function setEventHandlersPointerUnlockFn() {
                    document.removeEventListener('pointerlockchange', this.onPointerLockChanged, false);
                    document.removeEventListener('pointerlockerror', this.onPointerLockError, false);
                };
            }

            id.requestBrowserLock = function requestBrowserLockFn() {
                var pointerLockElement = (document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement);
                if (pointerLockElement !== canvas) {
                    this.setEventHandlersPointerLock();

                    requestPointerLock.call(canvas);
                }
            };

            id.requestBrowserUnlock = function requestBrowserUnlockFn() {
                this.setEventHandlersPointerUnlock();

                var pointerLockElement = (document.pointerLockElement || document.mozPointerLockElement || document.webkitPointerLockElement);
                if (pointerLockElement === canvas) {
                    exitPointerLock.call(document);
                }
            };
        } else {
            var pointer = (navigator.pointer || navigator.webkitPointer);
            if (pointer) {
                id.requestBrowserLock = function requestBrowserLockFn() {
                    if (!pointer.isLocked) {
                        pointer.lock(canvas);
                    }
                };

                id.requestBrowserUnlock = function requestBrowserUnlockFn() {
                    if (pointer.isLocked) {
                        pointer.unlock();
                    }
                };
            } else {
                id.requestBrowserLock = function requestBrowserLockFn() {
                };
                id.requestBrowserUnlock = function requestBrowserUnlockFn() {
                };
            }
        }

        // Add canvas mouse event listeners
        id.setEventHandlersCanvas();

        // Add window blur event listener
        id.setEventHandlersWindow();

        // Add canvas touch event listeners
        id.setEventHandlersTouch();

        // Record the platforms so that we can enable workarounds, etc.
        var sysInfo = TurbulenzEngine.getSystemInfo();
        id.macosx = ("Darwin" === sysInfo.osName);
        id.webkit = (/WebKit/.test(navigator.userAgent));

        return id;
    };
    WebGLInputDevice.version = 1;
    return WebGLInputDevice;
})();

// KeyCodes: List of key codes and their values
WebGLInputDevice.prototype.keyCodes = {
    A: 0,
    B: 1,
    C: 2,
    D: 3,
    E: 4,
    F: 5,
    G: 6,
    H: 7,
    I: 8,
    J: 9,
    K: 10,
    L: 11,
    M: 12,
    N: 13,
    O: 14,
    P: 15,
    Q: 16,
    R: 17,
    S: 18,
    T: 19,
    U: 20,
    V: 21,
    W: 22,
    X: 23,
    Y: 24,
    Z: 25,
    NUMBER_0: 100,
    NUMBER_1: 101,
    NUMBER_2: 102,
    NUMBER_3: 103,
    NUMBER_4: 104,
    NUMBER_5: 105,
    NUMBER_6: 106,
    NUMBER_7: 107,
    NUMBER_8: 108,
    NUMBER_9: 109,
    LEFT: 200,
    RIGHT: 201,
    UP: 202,
    DOWN: 203,
    LEFT_SHIFT: 300,
    RIGHT_SHIFT: 301,
    LEFT_CONTROL: 302,
    RIGHT_CONTROL: 303,
    LEFT_ALT: 304,
    RIGHT_ALT: 305,
    ESCAPE: 400,
    TAB: 401,
    SPACE: 402,
    BACKSPACE: 403,
    RETURN: 404,
    GRAVE: 500,
    MINUS: 501,
    EQUALS: 502,
    LEFT_BRACKET: 503,
    RIGHT_BRACKET: 504,
    SEMI_COLON: 505,
    APOSTROPHE: 506,
    COMMA: 507,
    PERIOD: 508,
    SLASH: 509,
    BACKSLASH: 510,
    F1: 600,
    F2: 601,
    F3: 602,
    F4: 603,
    F5: 604,
    F6: 605,
    F7: 606,
    F8: 607,
    F9: 608,
    F10: 609,
    F11: 610,
    F12: 611,
    NUMPAD_0: 612,
    NUMPAD_1: 613,
    NUMPAD_2: 614,
    NUMPAD_3: 615,
    NUMPAD_4: 616,
    NUMPAD_5: 617,
    NUMPAD_6: 618,
    NUMPAD_7: 619,
    NUMPAD_8: 620,
    NUMPAD_9: 621,
    NUMPAD_ENTER: 622,
    NUMPAD_DIVIDE: 623,
    NUMPAD_MULTIPLY: 624,
    NUMPAD_ADD: 625,
    NUMPAD_SUBTRACT: 626,
    LEFT_WIN: 627,
    RIGHT_WIN: 628,
    LEFT_OPTION: 629,
    RIGHT_OPTION: 630,
    CAPS_LOCK: 631,
    INSERT: 632,
    DELETE: 633,
    HOME: 634,
    END: 635,
    PAGE_UP: 636,
    PAGE_DOWN: 637,
    BACK: 638
};

WebGLInputDevice.prototype.mouseCodes = {
    BUTTON_0: 0,
    BUTTON_1: 1,
    BUTTON_2: 2,
    DELTA_X: 100,
    DELTA_Y: 101,
    MOUSE_WHEEL: 102
};

WebGLInputDevice.prototype.padCodes = {
    UP: 0,
    LEFT: 1,
    DOWN: 2,
    RIGHT: 3,
    A: 4,
    B: 5,
    X: 6,
    Y: 7,
    LEFT_TRIGGER: 8,
    RIGHT_TRIGGER: 9,
    LEFT_SHOULDER: 10,
    RIGHT_SHOULDER: 11,
    LEFT_THUMB: 12,
    LEFT_THUMB_X: 13,
    LEFT_THUMB_Y: 14,
    RIGHT_THUMB: 15,
    RIGHT_THUMB_X: 16,
    RIGHT_THUMB_Y: 17,
    START: 18,
    BACK: 19
};

// Copyright (c) 2011-2013 Turbulenz Limited
/* global debug: false*/
/* global VMath: false*/
/* global WebGLMathDevice: true*/
WebGLMathDevice = VMath;

/* debug.evaluate(function debugSetupMathDevice() {
    WebGLMathDevice = {
        _vmath: VMath,
        version: VMath.version,
        precision: VMath.precision,
        FLOAT_MAX: VMath.FLOAT_MAX,
        select: VMath.select,
        reciprocal: VMath.reciprocal,
        truncate: VMath.truncate,
        v2BuildZero: VMath.v2BuildZero,
        v2BuildOne: VMath.v2BuildOne,
        v2BuildXAxis: VMath.v2BuildXAxis,
        v2BuildYAxis: VMath.v2BuildYAxis,
        v2Build: function v2Fn(a, b, dst) {
            debug.assert(debug.isNumber(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2Build(a, b, dst);
        },
        v2Copy: VMath.v2Copy,
        v2Set: function v2SetFn(v, a) {
            debug.assert(debug.isMathType(v));
            debug.assert(debug.isNumber(a));
            return this._vmath.v2Set(v, a);
        },
        v2Neg: function v2NegFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2Neg(a, dst);
        },
        v2Add: function v2AddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Add(a, b, dst);
        },
        v2Add3: function v2Add3Fn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v2Add3(a, b, c, dst);
        },
        v2Add4: function v2Add4Fn(a, b, c, d, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            debug.assert(debug.isMathType(d));
            return this._vmath.v2Add4(a, b, c, d, dst);
        },
        v2Sub: function v2SubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Sub(a, b, dst);
        },
        v2Mul: function v2MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Mul(a, b, dst);
        },
        v2MulAdd: function v2MulAddFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v2MulAdd(a, b, c, dst);
        },
        v2Dot: function v2DotFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Dot(a, b);
        },
        v2PerpDot: function v2PerpDot(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2PerpDot(a, b);
        },
        v2LengthSq: function v2LengthSqFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2LengthSq(a);
        },
        v2Length: function v2LengthFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2Length(a);
        },
        v2Reciprocal: function v2ReciprocalFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2Reciprocal(a, dst);
        },
        v2Normalize: function v2NormalizeFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2Normalize(a, dst);
        },
        v2Abs: function v2AbsFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2Abs(a, dst);
        },
        v2Max: function v2MaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Max(a, b, dst);
        },
        v2Min: function v2MinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Min(a, b, dst);
        },
        v2Equal: function v2EqualFn(a, b, precision) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Equal(a, b, precision);
        },
        v2MaskEqual: function v2MaskEqualFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskEqual(a, b);
        },
        v2MaskLess: function v2MaskLessFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskLess(a, b);

            return [
                (a[0] < b[0]),
                (a[1] < b[1])
            ];
        },
        v2MaskGreater: function v2MaskGreaterFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskGreater(a, b);

            return [
                (a[0] > b[0]),
                (a[1] > b[1])
            ];
        },
        v2MaskGreaterEq: function v2MaskGreaterEqFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskGreaterEq(a, b);

            return [
                (a[0] >= b[0]),
                (a[1] >= b[1])
            ];
        },
        v2MaskNot: function v2MaskNotFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2MaskNot(a);

            return [
                !a[0],
                !a[1]
            ];
        },
        v2MaskOr: function v2MaskOrFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskOr(a, b);

            return [
                (a[0] || b[0]),
                (a[1] || b[1])
            ];
        },
        v2MaskAnd: function v2MaskAndFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2MaskAnd(a, b);

            return [
                (a[0] && b[0]),
                (a[1] && b[1])
            ];
        },
        v2Select: function v2SelectFn(m, a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v2Select(m, a, b, dst);
        },
        v2ScalarBuild: function v2ScalarBuildFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v2ScalarBuild(a, dst);
        },
        v2ScalarMax: function v2ScalarMaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2ScalarMax(a, b, dst);
        },
        v2ScalarMin: function v2ScalarMinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2ScalarMin(a, b, dst);
        },
        v2ScalarAdd: function v2ScalarAddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2ScalarAdd(a, b, dst);
        },
        v2ScalarSub: function v2ScalarSubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2ScalarSub(a, b, dst);
        },
        v2ScalarMul: function v2ScalarMulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2ScalarMul(a, b, dst);
        },
        v2AddScalarMul: function v2AddScalarMulFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(c));
            return this._vmath.v2AddScalarMul(a, b, c, dst);
        },
        v2EqualScalarMask: function v2EqualScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2EqualScalarMask(a, b);
        },
        v2LessScalarMask: function v2LessScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2LessScalarMask(a, b);
        },
        v2GreaterScalarMask: function v2GreaterScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2GreaterScalarMask(a, b);
        },
        v2GreaterEqScalarMask: function v2GreaterEqScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v2GreaterEqScalarMask(a, b);
        },
        v2Lerp: function v2LerpFn(a, b, t, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(t));
            return this._vmath.v2Lerp(a, b, t, dst);
        },
        // ------------------------------------------------------------------
        v3BuildZero: VMath.v3BuildZero,
        v3BuildOne: VMath.v3BuildOne,
        v3BuildXAxis: VMath.v3BuildXAxis,
        v3BuildYAxis: VMath.v3BuildYAxis,
        v3BuildZAxis: VMath.v3BuildZAxis,
        v3Build: function v3Fn(a, b, c, dst) {
            debug.assert(debug.isNumber(a));
            debug.assert(debug.isNumber(b));
            debug.assert(debug.isNumber(c));
            return this._vmath.v3Build(a, b, c, dst);
        },
        v3Copy: VMath.v3Copy,
        v3Set: function v3SetFn(v, a) {
            debug.assert(debug.isMathType(v));
            return this._vmath.v3Set(v, a);
        },
        v3Neg: function v3NegFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3Neg(a, dst);
        },
        v3Add: function v3AddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Add(a, b, dst);
        },
        v3Add3: function v3Add3Fn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v3Add3(a, b, c, dst);
        },
        v3Add4: function v3Add4Fn(a, b, c, d, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            debug.assert(debug.isMathType(d));
            return this._vmath.v3Add4(a, b, c, d, dst);
        },
        v3Sub: function v3SubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Sub(a, b, dst);
        },
        v3Mul: function v3MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Mul(a, b, dst);
        },
        v3MulAdd: function v3MulAddFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v3MulAdd(a, b, c, dst);
        },
        v3Dot: function v3DotFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Dot(a, b);
        },
        v3Cross: function v3CrossFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Cross(a, b, dst);
        },
        v3LengthSq: function v3LengthSqFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3LengthSq(a);
        },
        v3Length: function v3LengthFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3Length(a);
        },
        v3Reciprocal: function v3ReciprocalFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3Reciprocal(a, dst);
        },
        v3Normalize: function v3NormalizeFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3Normalize(a, dst);
        },
        v3Abs: function v3AbsFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3Abs(a, dst);
        },
        v3Max: function v3MaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Max(a, b, dst);
        },
        v3Min: function v3MinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Min(a, b, dst);
        },
        v3Equal: function v3EqualFn(a, b, precision) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Equal(a, b, precision);
        },
        v3MaskEqual: function v3MaskEqualFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskEqual(a, b);
        },
        v3MaskLess: function v3MaskLessFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskLess(a, b);
        },
        v3MaskGreater: function v3MaskGreaterFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskGreater(a, b);
        },
        v3MaskGreaterEq: function v3MaskGreaterEqFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskGreaterEq(a, b);
        },
        v3MaskNot: function v3MaskNotFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v3MaskNot(a);
        },
        v3MaskOr: function v3MaskOrFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskOr(a, b);
        },
        v3MaskAnd: function v3MaskAndFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3MaskAnd(a, b);
        },
        v3Select: function v3SelectFn(m, a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v3Select(m, a, b, dst);
        },
        v3ScalarBuild: function v3ScalarBuildFn(a, dst) {
            debug.assert(debug.isNumber(a));
            return this._vmath.v3ScalarBuild(a, dst);
        },
        v3ScalarMax: function v3ScalarMaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3ScalarMax(a, b, dst);
        },
        v3ScalarMin: function v3ScalarMinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3ScalarMin(a, b, dst);
        },
        v3ScalarAdd: function v3ScalarAddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3ScalarAdd(a, b, dst);
        },
        v3ScalarSub: function v3ScalarSubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3ScalarSub(a, b, dst);
        },
        v3ScalarMul: function v3ScalarMulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3ScalarMul(a, b, dst);
        },
        v3AddScalarMul: function v3AddScalarMulFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(c));
            return this._vmath.v3AddScalarMul(a, b, c, dst);
        },
        v3EqualScalarMask: function v3EqualScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3EqualScalarMask(a, b);
        },
        v3LessScalarMask: function v3LessScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3LessScalarMask(a, b);
        },
        v3GreaterScalarMask: function v3GreaterScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3GreaterScalarMask(a, b);
        },
        v3GreaterEqScalarMask: function v3GreaterEqScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v3GreaterEqScalarMask(a, b);
        },
        v3Lerp: function v3LerpFn(a, b, t, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(t));
            return this._vmath.v3Lerp(a, b, t, dst);
        },
        // ------------------------------------------------------------------
        v4BuildZero: VMath.v4BuildZero,
        v4BuildOne: VMath.v4BuildOne,
        v4Build: function v4BuildFn(a, b, c, d, dst) {
            debug.assert(debug.isNumber(a));
            debug.assert(debug.isNumber(b));
            debug.assert(debug.isNumber(c));
            debug.assert(debug.isNumber(d));
            return this._vmath.v4Build(a, b, c, d, dst);
        },
        v4Copy: VMath.v4Copy,
        v4Set: function v4SetFn(v, a) {
            debug.assert(debug.isMathType(v));
            return this._vmath.v4Set(v, a);
        },
        v4Neg: function v4NegFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4Neg(a, dst);
        },
        v4Add: function v4AddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Add(a, b, dst);
        },
        v4Add3: function v4Add3Fn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v4Add3(a, b, c, dst);
        },
        v4Add4: function v4Add4Fn(a, b, c, d, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            debug.assert(debug.isMathType(d));
            return this._vmath.v4Add4(a, b, c, d, dst);
        },
        v4Sub: function v4SubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Sub(a, b, dst);
        },
        v4Mul: function v4MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Mul(a, b, dst);
        },
        v4MulAdd: function v4MulAddFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isMathType(c));
            return this._vmath.v4MulAdd(a, b, c, dst);
        },
        v4Dot: function v4DotFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Dot(a, b);
        },
        v4LengthSq: function v4LengthSqFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4LengthSq(a);
        },
        v4Length: function v4LengthFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4Length(a);
        },
        v4Reciprocal: function v4ReciprocalFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4Reciprocal(a, dst);
        },
        v4Normalize: function v4NormalizeFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4Normalize(a, dst);
        },
        v4Abs: function v4AbsFn(a, dst) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4Abs(a, dst);
        },
        v4Max: function v4MaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Max(a, b, dst);
        },
        v4Min: function v4MinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Min(a, b, dst);
        },
        v4Equal: function v4EqualFn(a, b, precision) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(precision));
            return this._vmath.v4Equal(a, b, precision);
        },
        v4MaskEqual: function v4MaskEqualFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskEqual(a, b);
        },
        v4MaskLess: function v4MaskLessFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskLess(a, b);
        },
        v4MaskGreater: function v4MaskGreaterFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskGreater(a, b);
        },
        v4MaskGreaterEq: function v4MaskGreaterEqFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskGreaterEq(a, b);
        },
        v4MaskNot: function v4MaskNotFn(a) {
            debug.assert(debug.isMathType(a));
            return this._vmath.v4MaskNot(a);
        },
        v4MaskOr: function v4MaskOrFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskOr(a, b);
        },
        v4MaskAnd: function v4MaskAndFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4MaskAnd(a, b);
        },
        v4Many: function v4ManyFn(m) {
            return this._vmath.v4Many(m);
        },
        v4MaskAll: function v4MaskAllFn(m) {
            return this._vmath.v4MaskAll(m);
        },
        v4Select: function v4SelectFn(m, a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.v4Select(m, a, b, dst);
        },
        v4ScalarBuild: function v4ScalarBuildFn(a, dst) {
            debug.assert(debug.isNumber(a));
            return this._vmath.v4ScalarBuild(a, dst);
        },
        v4ScalarMax: function v4ScalarMaxFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarMax(a, b, dst);
        },
        v4ScalarMin: function v4ScalarMinFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarMin(a, b, dst);
        },
        v4ScalarAdd: function v4ScalarAddFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarAdd(a, b, dst);
        },
        v4ScalarSub: function v4ScalarSubFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarSub(a, b, dst);
        },
        v4ScalarMul: function v4ScalarMulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarMul(a, b, dst);
        },
        v4AddScalarMul: function v4AddScalarMulFn(a, b, c, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(c));
            return this._vmath.v4AddScalarMul(a, b, c, dst);
        },
        v4ScalarEqual: function v4ScalarEqualFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4ScalarEqual(a, b);
        },
        v4EqualScalarMask: function v4EqualScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4EqualScalarMask(a, b);
        },
        v4LessScalarMask: function v4LessScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4LessScalarMask(a, b);
        },
        v4GreaterScalarMask: function v4GreaterScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4GreaterScalarMask(a, b);
        },
        v4GreaterEqScalarMask: function v4GreaterEqScalarMaskFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isNumber(b));
            return this._vmath.v4GreaterEqScalarMask(a, b);
        },
        v4Lerp: function v4LerpFn(a, b, t, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(t));
            return this._vmath.v4Lerp(a, b, t, dst);
        },
        // ------------------------------------------------------------------
        aabbBuild: function aabbBuildFn(a0, a1, a2, a3, a4, a5, dst) {
            debug.assert(debug.isNumber(a0));
            debug.assert(debug.isNumber(a1));
            debug.assert(debug.isNumber(a2));
            debug.assert(debug.isNumber(a3));
            debug.assert(debug.isNumber(a4));
            debug.assert(debug.isNumber(a5));
            return this._vmath.aabbBuild(a0, a1, a2, a3, a4, a5, dst);
        },
        aabbBuildEmpty: VMath.aabbBuildEmpty,
        aabbCopy: VMath.aabbCopy,
        aabbSet: VMath.aabbSet,
        aabbIsEmpty: VMath.aabbIsEmpty,
        aabbMin: function aabbMinFn(aabb, dst) {
            debug.assert(debug.isMathType(aabb));
            return this._vmath.aabbMin(aabb, dst);
        },
        aabbMax: function aabbMaxFn(aabb, dst) {
            debug.assert(debug.isMathType(aabb));
            return this._vmath.aabbMax(aabb, dst);
        },
        aabbGetCenterAndHalf: function aabbGetCenterAndHalfFn(aabb, center, half) {
            debug.assert(debug.isMathType(aabb));
            debug.assert(debug.isMathType(center));
            debug.assert(debug.isMathType(half));
            return this._vmath.aabbGetCenterAndHalf(aabb, center, half);
        },
        aabbIsInsidePlanes: function aabbIsInsidePlanesFn(aabb, planes) {
            debug.assert(debug.isMathType(aabb));
            return this._vmath.aabbIsInsidePlanes(aabb, planes);
        },
        aabbIsFullyInsidePlanes: function aabbIsFullyInsidePlanesFn(aabb, planes) {
            debug.assert(debug.isMathType(aabb));
            return this._vmath.aabbIsFullyInsidePlanes(aabb, planes);
        },
        aabbUnion: function aabbUnionFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.aabbUnion(a, b, dst);
        },
        aabbUnionArray: VMath.aabbUnionArray,
        aabbAddPoints: function aabbAddPointFn(aabb, ps) {
            debug.assert(debug.isMathType(aabb));
            return this._vmath.aabbAddPoints(aabb, ps);
        },
        aabbTransform: function aabbTransformFn(aabb, matrix, dst) {
            debug.assert(debug.isMathType(aabb));
            debug.assert(debug.isMathType(matrix));
            return this._vmath.aabbTransform(aabb, matrix, dst);
        },
        aabbIntercept: function aabbInterceptFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.aabbIntercept(a, b, dst);
        },
        aabbOverlaps: function aabbOverlapsFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.aabbOverlaps(a, b);
        },
        aabbSphereOverlaps: function aabbSphereOverlapsFn(aabb, center, radius) {
            debug.assert(debug.isMathType(aabb));
            debug.assert(debug.isMathType(center));
            debug.assert(debug.isNumber(radius));
            return this._vmath.aabbSphereOverlaps(aabb, center, radius);
        },
        aabbIsInside: function aabbIsInsideFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.aabbIsInside(a, b);
        },
        aabbTestInside: function aabbTestInsideFn(a, b) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.aabbTestInside(a, b);
        },
        // ------------------------------------------------------------------
        m33BuildIdentity: VMath.m33BuildIdentity,
        m33Build: VMath.m33Build,
        m33Copy: VMath.m33Copy,
        m33FromAxisRotation: function m33FromAxisRotationFn(axis, angle, dst) {
            debug.assert(debug.isMathType(axis));
            debug.assert(debug.isNumber(angle));
            return this._vmath.m33FromAxisRotation(axis, angle, dst);
        },
        m33FromQuat: function m33FromQuatFn(q, dst) {
            debug.assert(debug.isMathType(q));
            return this._vmath.m33FromQuat(q, dst);
        },
        m33Right: function m33RightFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33Right(m, dst);
        },
        m33Up: function m33UpFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33Up(m, dst);
        },
        m33At: function m33AtFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33At(m, dst);
        },
        m33SetRight: function m33SetRightFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m33SetRight(m, v);
        },
        m33SetUp: function m33SetUpFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m33SetUp(m, v);
        },
        m33SetAt: function m33SetAtFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m33SetAt(m, v);
        },
        m33Transpose: function m33TransposeFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33Transpose(m, dst);
        },
        m33Determinant: function m33DeterminantFn(m) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33Determinant(m);
        },
        m33Inverse: function m33InverseFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33Inverse(m, dst);
        },
        m33InverseTranspose: function m33InverseTransposeFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m33InverseTranspose(m, dst);
        },
        m33Mul: function m33MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m33Mul(a, b, dst);
        },
        m33Transform: function m33TransformFn(m, v, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m33Transform(m, v, dst);
        },
        m33Equal: function m33EqualFn(a, b, precision) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            debug.assert(debug.isNumber(precision));
            return this._vmath.m33Equal(a, b, precision);
        },
        m33MulM43: function m33MulM43Fn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m33MulM43(a, b, dst);
        },
        m33MulM44: function m33MulM44Fn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m33MulM44(a, b, dst);
        },
        m33ScalarAdd: function m33ScalarAddFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m33ScalarAdd(m, s, dst);
        },
        m33ScalarSub: function m33ScalarSubFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m33ScalarSub(m, s, dst);
        },
        m33ScalarMul: function m33ScalarMulFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m33ScalarMul(m, s, dst);
        },
        // ------------------------------------------------------------------
        m34BuildIdentity: VMath.m34BuildIdentity,
        m34Pos: function m34PosFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m34Pos(m, dst);
        },
        m34Scale: function m34ScaleFn(m, scale, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(scale));
            return this._vmath.m34Scale(m, scale, dst);
        },
        // ------------------------------------------------------------------
        m43BuildIdentity: VMath.m43BuildIdentity,
        m43Build: VMath.m43Build,
        m43BuildTranslation: VMath.m43BuildTranslation,
        m43Copy: VMath.m43Copy,
        m43FromM33V3: function m43FromM33V3Fn(m, v, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43FromM33V3(m, v, dst);
        },
        m43FromAxisRotation: function m43FromAxisRotationFn(axis, angle, dst) {
            debug.assert(debug.isMathType(axis));
            debug.assert(debug.isNumber(angle));
            return this._vmath.m43FromAxisRotation(axis, angle, dst);
        },
        m43FromQuatPos: function m43FromQuatPosFn(qp, dst) {
            debug.assert(debug.isMathType(qp));
            return this._vmath.m43FromQuatPos(qp, dst);
        },
        m43FromRTS: function m43FromRTSFn(quat, pos, scale, dst) {
            debug.assert(debug.isMathType(quat));
            debug.assert(debug.isMathType(pos));
            debug.assert(debug.isMathType(scale));
            return this._vmath.m43FromRTS(quat, pos, scale, dst);
        },
        m43FromRT: function m43FromRTFn(quat, pos, dst) {
            debug.assert(debug.isMathType(quat));
            debug.assert(debug.isMathType(pos));
            return this._vmath.m43FromRT(quat, pos, dst);
        },
        m43Right: function m43RightFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Right(m, dst);
        },
        m43Up: function m43UpFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Up(m, dst);
        },
        m43At: function m43AtFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43At(m, dst);
        },
        m43Pos: function m43PosFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Pos(m, dst);
        },
        m43SetRight: function m43SetRightFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43SetRight(m, v);
        },
        m43SetUp: function m43SetUpFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43SetUp(m, v);
        },
        m43SetAt: function m43SetAtFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43SetAt(m, v);
        },
        m43SetPos: function m43SetPosFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43SetPos(m, v);
        },
        m43SetAxisRotation: function m43SetAxisRotationFn(m, axis, angle) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(axis));
            debug.assert(debug.isNumber(angle));
            return this._vmath.m43SetAxisRotation(m, axis, angle);
        },
        m43InverseOrthonormal: function m43InverseOrthonormalFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43InverseOrthonormal(m, dst);
        },
        m43Orthonormalize: function m43OrthonormalizeFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Orthonormalize(m, dst);
        },
        m43Determinant: function m43DeterminantFn(m) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Determinant(m);
        },
        m43Inverse: function m43InverseFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Inverse(m, dst);
        },
        m43Translate: function m43TranslateFn(matrix, pos) {
            debug.assert(debug.isMathType(matrix));
            debug.assert(debug.isMathType(pos));
            return this._vmath.m43Translate(matrix, pos);
        },
        m43Scale: function m43ScaleFn(m, scale, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(scale));
            return this._vmath.m43Scale(m, scale, dst);
        },
        m43TransformVector: function m43TransformVectorFn(m, v, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43TransformVector(m, v, dst);
        },
        m43TransformPoint: function m43TransformPointFn(m, v, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m43TransformPoint(m, v, dst);
        },
        m43Mul: function m43MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m43Mul(a, b, dst);
        },
        m43MulM44: function m43MulM44Fn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m43MulM44(a, b, dst);
        },
        m43Transpose: function m43TransposeFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m43Transpose(m, dst);
        },
        m43MulTranspose: function m43MulTransposeFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m43MulTranspose(a, b, dst);
        },
        m43Offset: function m43OffsetFn(m, o, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(o));
            return this._vmath.m43Offset(m, o, dst);
        },
        m43NegOffset: function m43NegOffsetFn(m, o, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(o));
            return this._vmath.m43NegOffset(m, o, dst);
        },
        m43InverseTransposeProjection: function m43InverseTransposeProjectionFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(s));
            return this._vmath.m43InverseTransposeProjection(m, s, dst);
        },
        m43ScalarAdd: function m43ScalarAddFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m43ScalarAdd(m, s, dst);
        },
        m43ScalarSub: function m43ScalarSubFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m43ScalarSub(m, s, dst);
        },
        m43ScalarMul: function m43ScalarMulFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m43ScalarMul(m, s, dst);
        },
        // ------------------------------------------------------------------
        m44BuildIdentity: VMath.m44BuildIdentity,
        m44Build: VMath.m44Build,
        m44Copy: VMath.m44Copy,
        m44Right: function m44RightFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44Right(m, dst);
        },
        m44Up: function m44UpFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44Up(m, dst);
        },
        m44At: function m44AtFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44At(m, dst);
        },
        m44Pos: function m44PosFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44Pos(m, dst);
        },
        m44SetRight: function m44SetRightFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44SetRight(m, v);
        },
        m44SetUp: function m44SetUpFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44SetUp(m, v);
        },
        m44SetAt: function m44SetAtFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44SetAt(m, v);
        },
        m44SetPos: function m44SetPosFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44SetPos(m, v);
        },
        m44Translate: function m44TranslateFn(m, v) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44Translate(m, v);
        },
        m44Scale: function m44ScaleFn(m, scale, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(scale));
            return this._vmath.m44Scale(m, scale, dst);
        },
        m44Transform: function m44TransformFn(m, v, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isMathType(v));
            return this._vmath.m44Transform(m, v, dst);
        },
        m44Mul: function m44MulFn(a, b, dst) {
            debug.assert(debug.isMathType(a));
            debug.assert(debug.isMathType(b));
            return this._vmath.m44Mul(a, b, dst);
        },
        m44Inverse: function m44InverseFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44Inverse(m, dst);
        },
        m44Transpose: function m44TransposeFn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.m44Transpose(m, dst);
        },
        m44ScalarAdd: function m44ScalarAddFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m44ScalarAdd(m, s, dst);
        },
        m44ScalarSub: function m44ScalarSubFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m44ScalarSub(m, s, dst);
        },
        m44ScalarMul: function m44ScalarMulFn(m, s, dst) {
            debug.assert(debug.isMathType(m));
            debug.assert(debug.isNumber(s));
            return this._vmath.m44ScalarMul(m, s, dst);
        },
        // ------------------------------------------------------------------
        quatBuild: VMath.quatBuild,
        quatCopy: VMath.quatCopy,
        quatIsSimilar: function quatIsSimilarFn(q1, q2, precision) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            debug.assert(debug.isNumber(precision));
            return this._vmath.quatIsSimilar(q1, q2, precision);
        },
        quatLength: function quatLengthFn(q) {
            debug.assert(debug.isMathType(q));
            return this._vmath.quatLength(q);
        },
        quatDot: function quatDotFn(q1, q2) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            return this._vmath.quatDot(q1, q2);
        },
        quatMul: function quatMulFn(q1, q2, dst) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            return this._vmath.quatMul(q1, q2, dst);
        },
        quatMulTranslate: function quatMulTranslateFn(qa, va, qb, vb, qr, vr) {
            debug.assert(debug.isMathType(qa));
            debug.assert(debug.isMathType(va));
            debug.assert(debug.isMathType(qb));
            debug.assert(debug.isMathType(vb));
            debug.assert(debug.isMathType(qr));
            debug.assert(debug.isMathType(vr));
            return this._vmath.quatMulTranslate(qa, va, qb, vb, qr, vr);
        },
        quatNormalize: function quatNormalizeFn(q, dst) {
            debug.assert(debug.isMathType(q));
            return this._vmath.quatNormalize(q, dst);
        },
        quatConjugate: function quatConjugateFn(q, dst) {
            debug.assert(debug.isMathType(q));
            return this._vmath.quatConjugate(q, dst);
        },
        quatLerp: function quatLerpFn(q1, q2, t, dst) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            debug.assert(debug.isNumber(t));
            return this._vmath.quatLerp(q1, q2, t, dst);
        },
        cosMinSlerpAngle: VMath.cosMinSlerpAngle,
        quatSlerp: function quatSlerpFn(q1, q2, t, dst) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            debug.assert(debug.isNumber(t));
            return this._vmath.quatSlerp(q1, q2, t, dst);
        },
        quatFromM43: function quatFromM43Fn(m, dst) {
            debug.assert(debug.isMathType(m));
            return this._vmath.quatFromM43(m, dst);
        },
        quatFromAxisRotation: function quatFromAxisRotationFn(axis, angle, dst) {
            debug.assert(debug.isMathType(axis));
            debug.assert(debug.isNumber(angle));
            return this._vmath.quatFromAxisRotation(axis, angle, dst);
        },
        quatToAxisRotation: function quatToAxisRotation(q, dst) {
            debug.assert(debug.isMathType(q));
            return this._vmath.quatToAxisRotation(q, dst);
        },
        quatTransformVector: function quatTransformVectorFn(q, v, dst) {
            debug.assert(debug.isMathType(q));
            debug.assert(debug.isMathType(v));
            return this._vmath.quatTransformVector(q, v, dst);
        },
        quatEqual: function quatEqual(q1, q2, precision) {
            debug.assert(debug.isMathType(q1));
            debug.assert(debug.isMathType(q2));
            debug.assert(debug.isNumber(precision));
            return this._vmath.quatEqual(q1, q2, precision);
        },
        quatPosBuild: function quatPosBuildFn(x, y, z, w, px, py, pz, dst) {
            if (arguments.length < 7) {
                debug.assert(debug.isMathType(x));
                debug.assert(debug.isMathType(y));
                return this._vmath.quatPosBuild(x, y, z);
            }
            return this._vmath.quatPosBuild(x, y, z, w, px, py, pz, dst);
        },
        quatPosTransformVector: function quatPosTransformVectorFn(qp, n, dst) {
            debug.assert(debug.isMathType(qp));
            return this._vmath.quatPosTransformVector(qp, n, dst);
        },
        quatPosTransformPoint: function quatPosTransformPointFn(qp, p) {
            debug.assert(debug.isMathType(qp));
            return this._vmath.quatPosTransformPoint(qp, p);
        },
        quatPosMul: function quatPosMulFn(qp1, qp2) {
            debug.assert(debug.isMathType(qp1));
            debug.assert(debug.isMathType(qp2));
            return this._vmath.quatPosMul(qp1, qp2);
        },
        // ------------------------------------------------------------------
        isVisibleBox: VMath.isVisibleBox,
        isVisibleBoxOrigin: VMath.isVisibleBoxOrigin,
        isVisibleSphere: VMath.isVisibleSphere,
        isVisibleSphereOrigin: VMath.isVisibleSphereOrigin,
        isVisibleSphereUnit: VMath.isVisibleSphereUnit,
        transformBox: VMath.transformBox,
        planeNormalize: VMath.planeNormalize,
        extractFrustumPlanes: VMath.extractFrustumPlanes,
        isInsidePlanesPoint: VMath.isInsidePlanesPoint,
        isInsidePlanesSphere: VMath.isInsidePlanesSphere,
        isInsidePlanesBox: VMath.isInsidePlanesBox,
        extractIntersectingPlanes: VMath.extractIntersectingPlanes
    };
}); */

// Copyright (c) 2011-2012 Turbulenz Limited

//
// WebGLNetworkDevice
//
var WebGLNetworkDevice = (function () {
    function WebGLNetworkDevice() {
    }
    WebGLNetworkDevice.prototype.createWebSocket = function (url, protocol) {
        var WebSocketConstructor = this.WebSocketConstructor;
        if (WebSocketConstructor) {
            var ws;
            if (protocol) {
                ws = new WebSocketConstructor(url, protocol);
            } else {
                ws = new WebSocketConstructor(url);
            }
            if (typeof ws.destroy === "undefined") {
                ws.destroy = function websocketDestroyFn() {
                    this.onopen = null;
                    this.onerror = null;
                    this.onclose = null;
                    this.onmessage = null;
                    this.close();
                };
            }
            return ws;
        } else {
            return null;
        }
    };

    WebGLNetworkDevice.prototype.update = function () {
    };

    WebGLNetworkDevice.create = function (params) {
        var nd = new WebGLNetworkDevice();
        return nd;
    };
    WebGLNetworkDevice.version = 1;
    return WebGLNetworkDevice;
})();

WebGLNetworkDevice.prototype.WebSocketConstructor = (window.WebSocket ? window.WebSocket : window.MozWebSocket);

// Copyright (c) 2011-2014 Turbulenz Limited
/*global Float32Array: false*/
/*global Uint16Array: false*/
/*global Uint32Array: false*/
/*global VMath: false*/
/*global AABBTree: false*/
/*global TurbulenzEngine: false*/

// -----------------------------------------------------------------------------
//
// WebGLPhysicsShape
//
var WebGLPhysicsShape = (function () {
    function WebGLPhysicsShape() {
    }
    WebGLPhysicsShape.version = 1;
    return WebGLPhysicsShape;
})();

//
// WebGLPhysicsConfig
//
var WebGLPhysicsConfig = {
    // (Contact physics)
    // Amount of slop permitted in contact penetration
    // And percentage of positional error to resolve
    // per simulation step.
    CONTACT_SLOP: 0.015,
    CONTACT_BAUMGRAUTE: 0.35,
    CONTACT_STATIC_BAUMGRAUTE: 0.65,
    //
    // (Contact persistance)
    // Amount of seperation permitted before contact cache is destroyed
    CONTACT_MAX_Y_SEPERATION: 0.05,
    // Amount of squared tangential seperation permitted before contact cache is destroyed
    CONTACT_MAX_SQ_XZ_SEPERATION: 2 * (0.245 * 0.245),
    // Amount of seperation permitted for a new contact to inherit existing cache's impulse cache
    CONTACT_INHERIT_SQ_SEPERATION: 3 * (0.75 * 0.75),
    // Amount of seperation to assume another contacts place instead of just inheriting
    CONTACT_EQUAL_SQ_SEPERATION: 3 * (0.001 * 0.001),
    //
    // (Collision detection)
    // seperation distance to assume objects are infact intersecting.
    GJK_EPA_DISTANCE_THRESHOLD: 1e-4,
    // fractional change in computed distance at which we may terminate GJK algorithm.
    GJK_FRACTIONAL_THRESHOLD: 1e-4,
    //
    // Threshold for the square of the ratio of velocity to radius of an object to be considered
    // moving fast enough to be collided continuously against static/sleeping objects.
    // this is multiplied with time step.
    CONTINUOUS_LINEAR_SQ: 0.35,
    // Threshold for square of angular velocity to be considered moving fast enough to be collided
    // continuously against static/sleeping objects.
    // this is multiplied with time step.
    CONTINUOUS_ANGULAR_SQ: 0.25,
    // Threshold for ratio of squared linear speed to radius for object to be considered moving fast enough
    // to be collided continuously against other dynamic objects.
    // This is a 'per-step' ratio.
    CONTINUOUS_LINEAR_BULLET: 0.75,
    // Threshold for squared angular speed to be considered for continuous collisions against other dynamics.
    // This is a 'per-step' value.
    CONTINUOUS_ANGULAR_BULLET: 0.5,
    // Amount of extra slop permitted in continuous collisions.
    // This is added ontop of the usual contact slop.
    CONTINUOUS_SLOP: 0.015,
    //
    // (Sleeping)
    // Threshold for the square of the ratio of velocity to radius of an object to
    // be considered at rest. Eg: if threshold is 1, then in a given second should the object
    // move less than 1x its radius, it will be considered at rest.
    SLEEP_LINEAR_SQ: 0.01,
    // squared angular velocity to be considered 'at rest'.
    // There is no scaling, as we base this on tangentenial velocity of body at radius which means
    // that when computing the ratio w.r.t to radius we end up simply with angular velocity.
    SLEEP_ANGULAR_SQ: 0.1,
    // number of world updates body must be 'at rest' to be allowed to sleep.
    SLEEP_DELAY: 60,
    //
    // (Misc)
    MAX_ANGULAR: Math.PI,
    //
    // (General)
    QUADRATIC_THRESHOLD: 1e-8,
    DONT_NORMALIZE_THRESHOLD: 1e-8,
    COLLINEAR_THRESHOLD: 1e-10,
    COPLANAR_THRESHOLD: 1e-16
};

var webGLPhysicsClone = function webGLPhysicsCloneFn(dst, src) {
    for (var p in src) {
        if (src.hasOwnProperty(p)) {
            var v = src[p];
            if (v === null || v === undefined) {
                continue;
            }

            if (typeof v === "object" && p !== "shape" && p !== "userData" && p !== "world" && p !== "object" && p !== "arbiters" && p !== "islandRoot" && p !== "island" && p !== "bodyA" && p !== "bodyB" && p !== "triangleArray") {
                if ("slice" in v) {
                    v = v.slice();
                } else {
                    v = webGLPhysicsClone({}, v);
                }
            }
            dst[p] = v;
        }
    }
    return dst;
};

var initShapeProperties = function initShapePropertiesFn(s, type, nomargin) {
    if (!nomargin) {
        Object.defineProperty(s, "margin", {
            get: function shapeGetMargin() {
                return this._private.collisionRadius;
            },
            set: function shapeSetMargin(margin) {
                var pr = this._private;
                pr.halfExtents[0] += (margin - pr.collisionRadius);
                pr.halfExtents[1] += (margin - pr.collisionRadius);
                pr.halfExtents[2] += (margin - pr.collisionRadius);
                pr.radius += (margin - pr.collisionRadius);

                pr.collisionRadius = margin;
            },
            enumerable: true
        });
    }

    Object.defineProperty(s, "halfExtents", {
        get: function shapeGetHalfExtents() {
            return VMath.v3Copy(this._private.halfExtents);
        },
        enumerable: true
    });

    Object.defineProperty(s, "inertia", {
        get: function shapeGetInertia() {
            return VMath.v3Copy(this._private.inertia);
        },
        enumerable: true
    });

    Object.defineProperty(s, "radius", {
        get: function shapeGetRadius() {
            return this._private.radius;
        },
        enumerable: true
    });

    Object.defineProperty(s, "type", {
        value: type,
        enumerable: true
    });
};

// TODO: probably better to inherit from WebGLPhysicsShape here
//
// WebGLPhysicsPlaneShape
//
var WebGLPhysicsPlaneShape = (function () {
    function WebGLPhysicsPlaneShape() {
    }
    WebGLPhysicsPlaneShape.prototype.rayTest = function (ray) {
        var dir = ray.direction;
        var origin = ray.origin;

        var dir0 = dir[0];
        var dir1 = dir[1];
        var dir2 = dir[2];
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];

        var normal = this.normal;
        var n0 = normal[0];
        var n1 = normal[1];
        var n2 = normal[2];

        //var dot = VMath.v3Dot(ray.direction, this.normal);
        var dot = ((dir0 * n0) + (dir1 * n1) + (dir2 * n2));

        if ((dot * dot) < WebGLPhysicsConfig.COPLANAR_THRESHOLD) {
            return null;
        }

        //var distance = (this.distance - VMath.v3Dot(ray.origin, this.normal)) / dot;
        var distance = ((this.distance - ((o0 * n0) + (o1 * n1) + (o2 * n2))) / dot);
        if (0 <= distance && distance <= ray.maxFactor) {
            if (dot > 0) {
                n0 = -n0;
                n1 = -n1;
                n2 = -n2;
            }

            //    hitPoint: VMath.v3Add(ray.origin, VMath.v3ScalarMul(ray.direction, distance)),
            var hit0 = (o0 + (dir0 * distance));
            var hit1 = (o1 + (dir1 * distance));
            var hit2 = (o2 + (dir2 * distance));
            return {
                factor: distance,
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build(n0, n1, n2)
            };
        } else {
            return null;
        }
    };

    WebGLPhysicsPlaneShape.create = function (params) {
        var retp = new WebGLPhysicsShape();
        var p = new WebGLPhysicsPlaneShape();
        retp._private = p;
        p._public = retp;

        p.collisionRadius = (params.margin !== undefined) ? params.margin : 0.04;
        p.distance = params.distance;
        var normal = p.normal = VMath.v3Copy(params.normal);

        var abs = Math.abs;
        var maxValue = Number.MAX_VALUE;

        p.radius = maxValue;

        var buffer = new Float32Array(6);

        if (abs(normal[0]) === 1) {
            p.halfExtents = VMath.v3Build(abs(p.distance), maxValue, maxValue, buffer.subarray(0, 3));
        } else if (abs(normal[1]) === 1) {
            p.halfExtents = VMath.v3Build(maxValue, abs(p.distance), maxValue, buffer.subarray(0, 3));
        } else if (abs(normal[2]) === 1) {
            p.halfExtents = VMath.v3Build(maxValue, maxValue, abs(p.distance), buffer.subarray(0, 3));
        }

        p.center = undefined;
        p.inertia = VMath.v3BuildZero(buffer.subarray(3, 6));

        initShapeProperties(retp, "PLANE");
        return retp;
    };
    WebGLPhysicsPlaneShape.version = 1;
    return WebGLPhysicsPlaneShape;
})();

WebGLPhysicsPlaneShape.prototype.type = "PLANE";

//
// WebGL Physics Capsule Shape
//
var WebGLPhysicsCapsuleShape = (function () {
    function WebGLPhysicsCapsuleShape() {
    }
    WebGLPhysicsCapsuleShape.prototype.rayTestCap = function (ray, height, scale) {
        var origin = ray.origin;
        var direction = ray.direction;
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var dir0 = direction[0];
        var dir1 = direction[1];
        var dir2 = direction[2];

        var radius = this.capsuleRadius;

        //Quadratic equation at^2 + bt + c = 0
        var a = ((dir0 * dir0) + (dir1 * dir1) + (dir2 * dir2));
        var dy = (o1 - height);
        var b = (2 * ((dir0 * o0) + (dir1 * dy) + (dir2 * o2)));
        var c = ((o0 * o0) + (dy * dy) + (o2 * o2) - (radius * radius));

        //Determinant
        var d = ((b * b) - (4 * a * c));
        if (d < 0) {
            return null;
        }

        var distance;
        var normalScale = 1.0;
        var hit1;

        var rec = (1 / (2 * a));
        var rootD = Math.sqrt(d);
        distance = ((-b - rootD) * rec);
        hit1 = (o1 + (dir1 * distance));
        if (distance < 0 || (scale * (hit1 - height) < 0)) {
            distance += (2 * rootD * rec);
            hit1 = (o1 + (dir1 * distance));
            normalScale = -1.0;
        }

        if ((scale * (hit1 - height) >= 0) && (0 <= distance && distance <= ray.maxFactor)) {
            var hit0 = (o0 + (dir0 * distance));
            var hit2 = (o2 + (dir2 * distance));
            var nScale = (normalScale / radius);
            return {
                factor: distance,
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build((hit0 * nScale), ((hit1 - height) * nScale), (hit2 * nScale))
            };
        } else {
            return null;
        }
    };

    WebGLPhysicsCapsuleShape.prototype.rayTest = function (ray) {
        var origin = ray.origin;
        var direction = ray.direction;
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var dir0 = direction[0];
        var dir1 = direction[1];
        var dir2 = direction[2];
        var maxFactor = ray.maxFactor;

        var radius = this.capsuleRadius;
        var halfHeight = this.halfHeight;
        var radius2 = (radius * radius);

        var distance;
        var normalScale = 1.0;
        var hit0;
        var hit1;
        var hit2;

        // Attempt to intersect capsule walls
        // Quadratic equation at^2 + bt + c = 0
        var a = ((dir0 * dir0) + (dir2 * dir2));
        if (a >= WebGLPhysicsConfig.QUADRATIC_THRESHOLD) {
            var b = (2 * ((o0 * dir0) + (o2 * dir2)));
            var c = ((o0 * o0) + (o2 * o2) - radius2);

            // Determinant
            var d = ((b * b) - (4 * a * c));
            var rec = (1 / (2 * a));

            if (d < WebGLPhysicsConfig.QUADRATIC_THRESHOLD) {
                distance = (-b * rec);
            } else if (d > 0) {
                var rootD = Math.sqrt(d);
                distance = ((-b - rootD) * rec);

                if (distance < 0) {
                    distance += (rootD * 2 * rec);
                    normalScale = -1.0;
                }
            }

            var scale;
            hit1 = (o1 + (dir1 * distance));
            if (-halfHeight <= hit1 && hit1 <= halfHeight) {
                if (0 <= distance && distance <= maxFactor) {
                    hit0 = (o0 + (dir0 * distance));
                    hit2 = (o2 + (dir2 * distance));
                    scale = (normalScale / radius);
                    return {
                        factor: distance,
                        hitPoint: VMath.v3Build(hit0, hit1, hit2),
                        hitNormal: VMath.v3Build((hit0 * scale), 0.0, (hit2 * scale))
                    };
                } else {
                    return null;
                }
            }
        }

        // Intersect capsule caps.
        return this.rayTestCap(ray, halfHeight, 1.0) || this.rayTestCap(ray, -halfHeight, -1.0);
    };

    WebGLPhysicsCapsuleShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        dst[0] = 0;
        dst[1] = (vec[1] >= 0) ? this.halfHeight : (-this.halfHeight);
        dst[2] = 0;
    };

    WebGLPhysicsCapsuleShape.create = function (params) {
        var retc = new WebGLPhysicsShape();
        var c = new WebGLPhysicsCapsuleShape();
        retc._private = c;
        c._public = retc;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var radius = params.radius;
        var height = params.height;
        var halfHeight = (0.5 * height);
        var maxRadius = (radius + halfHeight);

        var h0 = (radius + margin);
        var h1 = (maxRadius + margin);
        var h2 = (radius + margin);

        var lx = (2.0 * h0);
        var ly = (2.0 * h1);
        var lz = (2.0 * h2);
        lx *= lx;
        ly *= ly;
        lz *= lz;

        var massRatio = (1.0 / 12.0);

        var buffer = new Float32Array(6);

        c.radius = maxRadius + margin;
        c.capsuleRadius = radius;
        c.halfHeight = halfHeight;
        c.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        c.inertia = VMath.v3Build(massRatio * (ly + lz), massRatio * (lx + lz), massRatio * (lx + ly), buffer.subarray(3, 6));
        c.collisionRadius = radius + margin;

        c.center = undefined;

        // Defined differently from other shapes.
        Object.defineProperty(retc, "margin", {
            get: function capsuleShapeGetMargin() {
                return (this._private.collisionRadius - this._private.capsuleRadius);
            },
            set: function capsuleShapeSetMargin(margin) {
                var pr = this._private;
                pr.collisionRadius = (pr.capsuleRadius + margin);
                pr.halfExtents[0] = pr.capsuleRadius + margin;
                pr.halfExtents[1] = (pr.capsuleRadius + pr.halfHeight) + margin;
                pr.halfExtents[2] = pr.capsuleRadius + margin;
                pr.radius = (pr.capsuleRadius + pr.halfHeight) + margin;
            },
            enumerable: true
        });
        initShapeProperties(retc, "CAPSULE", true);
        return retc;
    };
    WebGLPhysicsCapsuleShape.version = 1;
    return WebGLPhysicsCapsuleShape;
})();

WebGLPhysicsShape.prototype.type = "CAPSULE";

//
// WebGL Physics Sphere Shape
//
var WebGLPhysicsSphereShape = (function () {
    function WebGLPhysicsSphereShape() {
    }
    WebGLPhysicsSphereShape.prototype.rayTest = function (ray) {
        var origin = ray.origin;
        var direction = ray.direction;
        var radius = this.sphereRadius;

        var dir0 = direction[0];
        var dir1 = direction[1];
        var dir2 = direction[2];
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];

        // Quadratic coeffecients at^2 + bt + c = 0
        // var a = VMath.v3Dot(direction, direction);
        // var b = 2 * VMath.v3Dot(origin, direction);
        // var c = VMath.v3Dot(origin, origin) - radius * radius;
        var a = ((dir0 * dir0) + (dir1 * dir1) + (dir2 * dir2));
        var b = (2 * ((o0 * dir0) + (o1 * dir1) + (o2 * dir2)));
        var c = (((o0 * o0) + (o1 * o1) + (o2 * o2)) - (radius * radius));

        var distance;

        // Determinant
        var d = ((b * b) - (4 * a * c));
        if (d <= 0) {
            return null;
        }

        var normalScale = 1.0;
        var rec = (1 / (2 * a));
        var rootD = Math.sqrt(d);
        distance = ((-b - rootD) * rec);
        if (distance < 0) {
            distance += (rootD * 2 * rec);
            normalScale = -1.0;
        }

        if (0 <= distance && distance < ray.maxFactor) {
            //hitPoint = VMath.v3Add(ray.origin, VMath.v3ScalarMul(ray.direction, distance));
            //hitNormal = VMath.v3ScalarDiv(hitPoint, radius * normalScale);
            var hit0 = (o0 + (dir0 * distance));
            var hit1 = (o1 + (dir1 * distance));
            var hit2 = (o2 + (dir2 * distance));

            var scale = (normalScale / radius);
            return {
                factor: distance,
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build((hit0 * scale), (hit1 * scale), (hit2 * scale))
            };
        } else {
            return null;
        }
    };

    WebGLPhysicsSphereShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        dst[0] = dst[1] = dst[2] = 0;
    };

    WebGLPhysicsSphereShape.create = function (params) {
        var rets = new WebGLPhysicsShape();
        var s = new WebGLPhysicsSphereShape();
        rets._private = s;
        s._public = rets;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var radius = params.radius;
        var i = (0.4 * radius * radius);

        var buffer = new Float32Array(6);

        s.sphereRadius = radius;
        s.radius = s.sphereRadius + margin;
        s.collisionRadius = radius + margin;
        s.halfExtents = VMath.v3Build(radius + margin, radius + margin, radius + margin, buffer.subarray(0, 3));
        s.inertia = VMath.v3Build(i, i, i, buffer.subarray(3, 6));

        s.center = undefined;

        // Defined differently from other shapes.
        Object.defineProperty(rets, "margin", {
            get: function sphereShapeGetMargin() {
                return (this._private.collisionRadius - this._private.radius);
            },
            set: function sphereShapeSetMargin(margin) {
                var pr = this._private;
                pr.collisionRadius = (pr.radius + margin);
                pr.halfExtents[0] = pr.collisionRadius;
                pr.halfExtents[1] = pr.collisionRadius;
                pr.halfExtents[2] = pr.collisionRadius;
                pr.radius = pr.collisionRadius;
            },
            enumerable: true
        });
        initShapeProperties(rets, "SPHERE", true);
        return rets;
    };
    WebGLPhysicsSphereShape.version = 1;
    return WebGLPhysicsSphereShape;
})();

WebGLPhysicsSphereShape.prototype.type = "SPHERE";

//
// WebGL Physics Box Shape
//
var WebGLPhysicsBoxShape = (function () {
    function WebGLPhysicsBoxShape() {
    }
    WebGLPhysicsBoxShape.prototype.rayTest = function (ray) {
        var origin = ray.origin;
        var direction = ray.direction;
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var d0 = direction[0];
        var d1 = direction[1];
        var d2 = direction[2];

        var halfExtents = this.halfExtents;
        var h0 = halfExtents[0];
        var h1 = halfExtents[1];
        var h2 = halfExtents[2];

        var minDistance;
        var axis;

        // Code is similar for all pairs of faces.
        // Could be moved to a function, but would have performance penalty.
        //
        // In each case we check (Assuming that ray is not horizontal to plane)
        // That the ray is able to intersect one or both of the faces' planes
        // based on direction, origin and half extents.
        //                        |    |
        // cannot intersect <--o  |    | o--> cannot intersect
        //                        |    |
        //
        // If ray is able to intersect planes, we choose which face to intersect
        // with based on direction, origin and half extents and perform intersection.
        //                           |           |
        //                           | o--> pos. | <--o intersect pos. face
        // intersect neg. face o-->  | neg. <--o |
        //                           |           |
        //
        // intersect with yz faces.
        var t, f, hx, hy;
        if (d0 !== 0 && ((d0 > 0 && o0 <= -h0) || (d0 < 0 && o0 >= h0))) {
            f = (d0 > 0 ? (o0 >= -h0 ? h0 : -h0) : (o0 <= h0 ? -h0 : h0));
            t = (f - o0) / d0;
            if (minDistance === undefined || t < minDistance) {
                hx = o1 + (d1 * t);
                hy = o2 + (d2 * t);
                if ((-h1 <= hx && hx <= h1) && (-h2 <= hy && hy <= h2)) {
                    minDistance = t;
                    axis = 0;
                }
            }
        }

        if (d1 !== 0 && ((d1 > 0 && o1 <= -h1) || (d1 < 0 && o1 >= h1))) {
            f = (d1 > 0 ? (o1 >= -h1 ? h1 : -h1) : (o1 <= h1 ? -h1 : h1));
            t = (f - o1) / d1;
            if (minDistance === undefined || t < minDistance) {
                hx = o0 + (d0 * t);
                hy = o2 + (d2 * t);
                if ((-h0 <= hx && hx <= h0) && (-h2 <= hy && hy <= h2)) {
                    minDistance = t;
                    axis = 1;
                }
            }
        }

        if (d2 !== 0 && ((d2 > 0 && o2 <= -h2) || (d2 < 0 && o2 >= h2))) {
            f = (d2 > 0 ? (o2 >= -h2 ? h2 : -h2) : (o2 <= h2 ? -h2 : h2));
            t = (f - o2) / d2;
            if (minDistance === undefined || t < minDistance) {
                hx = o1 + (d1 * t);
                hy = o0 + (d0 * t);
                if ((-h1 <= hx && hx <= h1) && (-h0 <= hy && hy <= h0)) {
                    minDistance = t;
                    axis = 2;
                }
            }
        }

        if (minDistance !== undefined && minDistance < ray.maxFactor) {
            return {
                hitPoint: VMath.v3Build(o0 + d0 * minDistance, o1 + d1 * minDistance, o2 + d2 * minDistance),
                hitNormal: VMath.v3Build(axis === 0 ? (d0 > 0 ? -1 : 1) : 0, axis === 1 ? (d1 > 0 ? -1 : 1) : 0, axis === 2 ? (d2 > 0 ? -1 : 1) : 0),
                factor: minDistance
            };
        } else {
            return null;
        }
    };

    WebGLPhysicsBoxShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        var v0 = vec[0];
        var v1 = vec[1];
        var v2 = vec[2];

        var halfExtents = this.halfExtents;
        var h0 = halfExtents[0];
        var h1 = halfExtents[1];
        var h2 = halfExtents[2];

        dst[0] = ((v0 < 0) ? -h0 : h0);
        dst[1] = ((v1 < 0) ? -h1 : h1);
        dst[2] = ((v2 < 0) ? -h2 : h2);
    };

    WebGLPhysicsBoxShape.create = function (params) {
        var retb = new WebGLPhysicsShape();
        var b = new WebGLPhysicsBoxShape();
        retb._private = b;
        b._public = retb;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var halfExtents = params.halfExtents;

        var h0 = (halfExtents[0] + margin);
        var h1 = (halfExtents[1] + margin);
        var h2 = (halfExtents[2] + margin);

        var lx = (2.0 * h0);
        var ly = (2.0 * h1);
        var lz = (2.0 * h2);
        lx *= lx;
        ly *= ly;
        lz *= lz;

        var buffer = new Float32Array(6);

        b.center = undefined;

        b.radius = Math.sqrt((h0 * h0) + (h1 * h1) + (h2 * h2));
        b.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        b.inertia = VMath.v3Build((1.0 / 12.0) * (ly + lz), (1.0 / 12.0) * (lx + lz), (1.0 / 12.0) * (lx + ly), buffer.subarray(3, 6));
        b.collisionRadius = margin;

        initShapeProperties(retb, "BOX");
        return retb;
    };
    WebGLPhysicsBoxShape.version = 1;
    return WebGLPhysicsBoxShape;
})();

WebGLPhysicsBoxShape.prototype.type = "BOX";

//
// WebGL Physics Cylinder Shape
//
var WebGLPhysicsCylinderShape = (function () {
    function WebGLPhysicsCylinderShape() {
    }
    WebGLPhysicsCylinderShape.prototype.rayTest = function (ray) {
        var origin = ray.origin;
        var direction = ray.direction;
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var dir0 = direction[0];
        var dir1 = direction[1];
        var dir2 = direction[2];
        var maxFactor = ray.maxFactor;

        var radius = this.cylinderRadius;
        var halfHeight = this.halfHeight;
        var radius2 = radius * radius;

        // Attempt to intersect cylinder walls
        // Quadratic equation at^2 + bt + c = 0
        var a = ((dir0 * dir0) + (dir2 * dir2));
        var b = (2 * ((o0 * dir0) + (o2 * dir2)));
        var c = ((o0 * o0) + (o2 * o2) - radius2);

        var distance;
        var normalScale = 1.0;
        var hit0, hit1, hit2;
        var scale, rec;

        // Determinant
        var d = ((b * b) - (4 * a * c));
        if (d >= 0) {
            rec = (1 / (2 * a));
            var rootD = Math.sqrt(d);
            distance = ((-b - rootD) * rec);

            if (distance < 0) {
                distance += (rootD * 2 * rec);
                normalScale = -1.0;
            }

            hit1 = (o1 + (dir1 * distance));
            if (-halfHeight <= hit1 && hit1 <= halfHeight) {
                if (0 <= distance && distance <= maxFactor) {
                    hit0 = (o0 + (dir0 * distance));
                    hit2 = (o2 + (dir2 * distance));
                    scale = (normalScale / radius);
                    return {
                        factor: distance,
                        hitPoint: VMath.v3Build(hit0, hit1, hit2),
                        hitNormal: VMath.v3Build((hit0 * scale), 0.0, (hit2 * scale))
                    };
                } else {
                    return null;
                }
            }
        }

        if ((dir1 * dir1) >= WebGLPhysicsConfig.COPLANAR_THRESHOLD) {
            scale = ((dir1 < 0) ? -1.0 : 1.0);
            hit1 = (-scale * halfHeight);
            rec = (1 / dir1);
            distance = ((hit1 - o1) * rec);

            if (distance < 0) {
                hit1 = (scale * halfHeight);
                distance = ((hit1 - o1) * rec);
            }

            if (0 <= distance && distance <= maxFactor) {
                hit0 = (o0 + (dir0 * distance));
                hit2 = (o2 + (dir2 * distance));
                if (((hit0 * hit0) + (hit2 * hit2)) <= radius2) {
                    return {
                        factor: distance,
                        hitPoint: VMath.v3Build(hit0, hit1, hit2),
                        hitNormal: VMath.v3Build(0.0, -scale, 0.0)
                    };
                }
            }
        }

        return null;
    };

    WebGLPhysicsCylinderShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        var v0 = vec[0];
        var v2 = vec[2];
        var vmag2 = ((v0 * v0) + (v2 * v2));
        if (vmag2 === 0) {
            if (vec[1] > 0) {
                dst[0] = this.cylinderRadius;
                dst[1] = this.halfHeight;
                dst[2] = 0;
            } else {
                dst[0] = 0;
                dst[1] = -this.halfHeight;
                dst[2] = -this.cylinderRadius;
            }
            return;
        }

        var scale = (this.cylinderRadius / Math.sqrt(vmag2));
        dst[0] = (v0 * scale);
        dst[1] = ((vec[1] > 0 ? 1 : -1) * this.halfHeight);
        dst[2] = (v2 * scale);
    };

    WebGLPhysicsCylinderShape.create = function (params) {
        var retc = new WebGLPhysicsShape();
        var c = new WebGLPhysicsCylinderShape();
        retc._private = c;
        c._public = retc;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var halfExtents = params.halfExtents;

        var h0 = (halfExtents[0] + margin);
        var h1 = (halfExtents[1] + margin);
        var h2 = (halfExtents[2] + margin);

        var radius2 = (h0 * h0);
        var height2 = (4.0 * h1 * h1);

        var t1 = (((1.0 / 12.0) * height2) + ((1.0 / 4.0) * radius2));
        var t2 = ((1.0 / 2.0) * radius2);

        var buffer = new Float32Array(6);

        c.center = undefined;

        c.radius = Math.sqrt((h0 * h0) + (h1 * h1) + (h2 * h2));
        c.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        c.cylinderRadius = halfExtents[0];
        c.halfHeight = halfExtents[1];
        c.inertia = VMath.v3Build(t1, t2, t1, buffer.subarray(3, 6));
        c.collisionRadius = margin;

        initShapeProperties(retc, "CYLINDER");
        return retc;
    };
    WebGLPhysicsCylinderShape.version = 1;
    return WebGLPhysicsCylinderShape;
})();

WebGLPhysicsCylinderShape.prototype.type = "CYLINDER";

//
// WebGLPhysicsConeShape
//
var WebGLPhysicsConeShape = (function () {
    function WebGLPhysicsConeShape() {
    }
    WebGLPhysicsConeShape.prototype.rayTest = function (ray) {
        var origin = ray.origin;
        var direction = ray.direction;
        var o0 = origin[0];
        var o1 = origin[1];
        var o2 = origin[2];
        var dir0 = direction[0];
        var dir1 = direction[1];
        var dir2 = direction[2];
        var maxFactor = ray.maxFactor;

        var radius = this.coneRadius;
        var halfHeight = this.halfHeight;

        var conicK = (radius / (2 * halfHeight));
        conicK *= conicK;

        // Intersect with conic surface.
        //
        // Quadratic equation at^2 + bt + c = 0
        var d1 = o1 - halfHeight;
        var a = (dir0 * dir0) + (dir2 * dir2) - (conicK * dir1 * dir1);
        var b = 2 * ((o0 * dir0) + (o2 * dir2) - (conicK * d1 * dir1));
        var c = (o0 * o0) + (o2 * o2) - (conicK * d1 * d1);

        var distance;
        var normalScale = 1.0;
        var hit0, hit1, hit2;

        // Determinant
        var d = ((b * b) - (4 * a * c));
        if (d >= 0) {
            var rec = (1 / (2 * a));
            var rootD = Math.sqrt(d);
            distance = ((-b - rootD) * rec);
            hit1 = (o1 + (dir1 * distance));
            if (distance < 0 || hit1 < -halfHeight || hit1 > halfHeight) {
                distance += (2 * rootD * rec);
                normalScale = -1.0;
                hit1 = (o1 + (dir1 * distance));
                if (distance < 0 || hit1 < -halfHeight || hit1 > halfHeight) {
                    distance = undefined;
                }
            }
        }

        // Intersect with cone cap.
        var t;
        if (dir1 !== 0) {
            t = (-halfHeight - o1) / dir1;
            hit0 = (o0 + (dir0 * t));
            hit2 = (o2 + (dir2 * t));
            if (t < 0 || ((hit0 * hit0) + (hit2 * hit2)) > (radius * radius)) {
                t = undefined;
            }
        }

        if (t === undefined && distance === undefined) {
            return null;
        }

        if (t === undefined || (distance !== undefined && distance < t)) {
            if (distance >= maxFactor) {
                return null;
            }

            hit0 = (o0 + (dir0 * distance));
            hit1 = (o1 + (dir1 * distance));
            hit2 = (o2 + (dir2 * distance));

            var n1 = conicK * (hit1 - halfHeight);
            var scale = normalScale / Math.sqrt((hit0 * hit0) + (n1 * n1) + (hit2 * hit2));

            return {
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build(scale * hit0, scale * n1, scale * hit2),
                factor: distance
            };
        } else {
            if (t >= maxFactor) {
                return null;
            }

            hit0 = (o0 + (dir0 * t));
            hit1 = (o1 + (dir1 * t));
            hit2 = (o2 + (dir2 * t));
            return {
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build(0, ((o1 < -halfHeight) ? -1 : 1), 0),
                factor: t
            };
        }
    };

    WebGLPhysicsConeShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        var v0 = vec[0];
        var v1 = vec[1];
        var v2 = vec[2];

        var vxz = Math.sqrt((v0 * v0) + (v2 * v2));
        if (((-this.coneRadius * vxz) + (2 * this.halfHeight * v1)) > 0) {
            dst[0] = dst[2] = 0;
            dst[1] = this.halfHeight;
        } else {
            if (vxz === 0) {
                dst[0] = this.coneRadius;
                dst[2] = 0;
            } else {
                dst[0] = (v0 * this.coneRadius / vxz);
                dst[2] = (v2 * this.coneRadius / vxz);
            }
            dst[1] = -this.halfHeight;
        }
    };

    WebGLPhysicsConeShape.create = function (params) {
        var retc = new WebGLPhysicsShape();
        var c = new WebGLPhysicsConeShape();
        retc._private = c;
        c._public = retc;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var radius = params.radius;
        var height = params.height;
        var halfHeight = (0.5 * height);

        var h0 = (radius + margin);
        var h1 = (halfHeight + margin);
        var h2 = (radius + margin);

        var lx = (2.0 * h0);
        var ly = (2.0 * h1);
        var lz = (2.0 * h2);
        lx *= lx;
        ly *= ly;
        lz *= lz;

        var massRatio = (1.0 / 12.0);

        var buffer = new Float32Array(6);

        c.halfHeight = halfHeight;
        c.coneRadius = radius;
        c.radius = Math.sqrt((h0 * h0) + (h1 * h1) + (h2 * h2));
        c.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        c.inertia = VMath.v3Build(massRatio * (ly + lz), massRatio * (lx + lz), massRatio * (lx + ly), buffer.subarray(3, 6));
        c.collisionRadius = margin;

        c.center = undefined;

        initShapeProperties(retc, "CONE");
        return retc;
    };
    WebGLPhysicsConeShape.version = 1;
    return WebGLPhysicsConeShape;
})();

WebGLPhysicsConeShape.prototype.type = "CONE";

//
// WebGLPhysicsTriangleArray
//
var WebGLPhysicsTriangleArray = (function () {
    function WebGLPhysicsTriangleArray() {
    }
    WebGLPhysicsTriangleArray.create = function (params) {
        var rett = new WebGLPhysicsTriangleArray();
        var t = new WebGLPhysicsPrivateTriangleArray();
        rett._private = t;
        t._public = rett;

        var vertices = params.vertices;
        var numVertices = (vertices.length / 3);
        var indices = params.indices;
        var numTriangles = (indices.length / 3);

        var minExtent = params.minExtent;
        var maxExtent = params.maxExtent;

        var v0;
        var v1;
        var v2;

        if (!minExtent || !maxExtent) {
            var min0 = vertices[0];
            var min1 = vertices[1];
            var min2 = vertices[2];
            var max0 = min0;
            var max1 = min1;
            var max2 = min2;
            var maxN = vertices.length;
            for (var n = 3; n < maxN; n += 3) {
                v0 = vertices[n];
                v1 = vertices[n + 1];
                v2 = vertices[n + 2];
                if (min0 > v0) {
                    min0 = v0;
                } else if (max0 < v0) {
                    max0 = v0;
                }
                if (min1 > v1) {
                    min1 = v1;
                } else if (max1 < v1) {
                    max1 = v1;
                }
                if (min2 > v2) {
                    min2 = v2;
                } else if (max2 < v2) {
                    max2 = v2;
                }
            }
            minExtent = [min0, min1, min2];
            maxExtent = [max0, max1, max2];
        }

        var extents = new Float32Array(6);
        extents[0] = minExtent[0];
        extents[1] = minExtent[1];
        extents[2] = minExtent[2];
        extents[3] = maxExtent[0];
        extents[4] = maxExtent[1];
        extents[5] = maxExtent[2];

        t.vertices = (params.dontCopy ? vertices : new Float32Array(vertices));
        t.numVertices = numVertices;
        t.indices = (params.dontCopy ? indices : (numVertices < 65536 ? new Uint16Array(indices) : new Uint32Array(indices)));
        t.numTriangles = numTriangles;
        t.extents = extents;

        // read only, no getter needed.
        Object.defineProperty(rett, "vertices", {
            value: t.vertices,
            enumerable: true
        });
        Object.defineProperty(rett, "indices", {
            value: t.indices,
            enumerable: true
        });

        /*
        store pre-computed triangle information for ray tests
        
        n0 n1 n2 - triangle normal
        v0 v1 v2 - triangle vertex
        u0 u1 u2 v0 v1 v2  - edge vectors
        dotuu dotvv dotuv negLimit - barycentric constants
        d - triangle plane distance
        */
        var triangles = new Float32Array(WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE * numTriangles);
        var spatialMap = null;

        if (numTriangles >= 8) {
            spatialMap = AABBTree.create(true);
            extents = new Float32Array(6);
        }

        var i;
        for (i = 0; i < numTriangles; i = i + 1) {
            var i3 = (i * 3);
            var itri = (i * WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE);

            var i0 = (indices[i3] * 3);
            var i1 = (indices[i3 + 1] * 3);
            var i2 = (indices[i3 + 2] * 3);

            var v00 = vertices[i0];
            var v01 = vertices[i0 + 1];
            var v02 = vertices[i0 + 2];

            var v10 = vertices[i1];
            var v11 = vertices[i1 + 1];
            var v12 = vertices[i1 + 2];

            var v20 = vertices[i2];
            var v21 = vertices[i2 + 1];
            var v22 = vertices[i2 + 2];

            //var u = VMath.v3Sub(v1, v0);
            //var v = VMath.v3Sub(v2, v0);
            var u0 = (v10 - v00);
            var u1 = (v11 - v01);
            var u2 = (v12 - v02);
            v0 = (v20 - v00);
            v1 = (v21 - v01);
            v2 = (v22 - v02);

            //var normal = VMath.v3Cross(u, v);
            var n0 = ((u1 * v2) - (u2 * v1));
            var n1 = ((u2 * v0) - (u0 * v2));
            var n2 = ((u0 * v1) - (u1 * v0));
            var nn = (1.0 / Math.sqrt((n0 * n0) + (n1 * n1) + (n2 * n2)));

            var distance = (((n0 * v00) + (n1 * v01) + (n2 * v02)) * nn);

            //var dotuv = VMath.v3Dot(u, v);
            //var dotuu = VMath.v3Dot(u, u);
            //var dotvv = VMath.v3Dot(v, v);
            var dotuv = ((u0 * v0) + (u1 * v1) + (u2 * v2));
            var dotuu = ((u0 * u0) + (u1 * u1) + (u2 * u2));
            var dotvv = ((v0 * v0) + (v1 * v1) + (v2 * v2));

            // Always negative
            var negLimit = ((dotuv * dotuv) - (dotuu * dotvv));

            triangles[itri] = (n0 * nn);
            triangles[itri + 1] = (n1 * nn);
            triangles[itri + 2] = (n2 * nn);
            triangles[itri + 3] = v00;
            triangles[itri + 4] = v01;
            triangles[itri + 5] = v02;
            triangles[itri + 6] = u0;
            triangles[itri + 7] = u1;
            triangles[itri + 8] = u2;
            triangles[itri + 9] = v0;
            triangles[itri + 10] = v1;
            triangles[itri + 11] = v2;
            triangles[itri + 12] = dotuu;
            triangles[itri + 13] = dotvv;
            triangles[itri + 14] = dotuv;
            triangles[itri + 15] = negLimit;
            triangles[itri + 16] = distance;

            if (spatialMap) {
                extents[0] = Math.min(v00, v10, v20);
                extents[1] = Math.min(v01, v11, v21);
                extents[2] = Math.min(v02, v12, v22);
                extents[3] = Math.max(v00, v10, v20);
                extents[4] = Math.max(v01, v11, v21);
                extents[5] = Math.max(v02, v12, v22);

                var triNode = {
                    index: itri,
                    spatialIndex: undefined
                };
                spatialMap.add(triNode, extents);
            }
        }

        if (spatialMap) {
            spatialMap.finalize();
        }

        t.triangles = triangles;
        t.spatialMap = spatialMap;

        return rett;
    };
    WebGLPhysicsTriangleArray.version = 1;
    return WebGLPhysicsTriangleArray;
})();

var WebGLPhysicsPrivateTriangleArray = (function () {
    function WebGLPhysicsPrivateTriangleArray() {
    }
    WebGLPhysicsPrivateTriangleArray.prototype.rayTest = function (ray) {
        var triangles = this.triangles;
        var spatialMap = this.spatialMap;

        function rayCallback(tree, triangle, ray, unusedAABBDistance, upperBound) {
            var dir = ray.direction;
            var dir0 = dir[0];
            var dir1 = dir[1];
            var dir2 = dir[2];

            var origin = ray.origin;
            var o0 = origin[0];
            var o1 = origin[1];
            var o2 = origin[2];

            var i = triangle.index;
            var n0 = triangles[i];
            var n1 = triangles[i + 1];
            var n2 = triangles[i + 2];

            //var dot = VMath.v3Dot(ray.direction, normal);
            var dot = ((dir0 * n0) + (dir1 * n1) + (dir2 * n2));

            if ((dot * dot) < WebGLPhysicsConfig.COPLANAR_THRESHOLD) {
                return null;
            }

            var d = triangles[i + 16];
            var v00 = triangles[i + 3];
            var v01 = triangles[i + 4];
            var v02 = triangles[i + 5];

            //var distance = VMath.v3Dot(VMath.v3Sub(v0, ray.origin), normal) / dot;
            var distance = ((d - ((o0 * n0) + (o1 * n1) + (o2 * n2))) / dot);
            if (distance < 0 || distance >= upperBound) {
                return null;
            }

            if (dot > 0) {
                //normal = VMath.v3Neg(normal);
                n0 = -n0;
                n1 = -n1;
                n2 = -n2;

                dot = -dot;
            }

            //var hitPoint = VMath.v3Add(ray.origin, VMath.v3ScalarMul(ray.direction, distance));
            var hit0 = (o0 + (dir0 * distance));
            var hit1 = (o1 + (dir1 * distance));
            var hit2 = (o2 + (dir2 * distance));

            // Compute barycentric coordinates in triangle.
            //var w = VMath.v3Sub(hitPoint, v0);
            var wx = (hit0 - v00);
            var wy = (hit1 - v01);
            var wz = (hit2 - v02);

            var dotuu = triangles[i + 12];
            var dotvv = triangles[i + 13];
            var dotuv = triangles[i + 14];
            var negLimit = triangles[i + 15];

            var u0 = triangles[i + 6];
            var u1 = triangles[i + 7];
            var u2 = triangles[i + 8];
            var v0 = triangles[i + 9];
            var v1 = triangles[i + 10];
            var v2 = triangles[i + 11];

            //var dotwu = VMath.v3Dot(w, u);
            //var dotwv = VMath.v3Dot(w, v);
            var dotwu = (wx * u0) + (wy * u1) + (wz * u2);
            var dotwv = (wx * v0) + (wy * v1) + (wz * v2);

            var alpha = ((dotuv * dotwv) - (dotvv * dotwu));
            if (alpha > 0 || alpha < negLimit) {
                return null;
            }

            var beta = ((dotuv * dotwu) - (dotuu * dotwv));
            if (beta > 0 || (alpha + beta) < negLimit) {
                return null;
            }

            return {
                factor: distance,
                hitPoint: VMath.v3Build(hit0, hit1, hit2),
                hitNormal: VMath.v3Build(n0, n1, n2)
            };
        }

        if (spatialMap) {
            return AABBTree.rayTest([spatialMap], ray, rayCallback);
        } else {
            var minimumResult = null;
            var upperBound = ray.maxFactor;

            var triNode = {
                index: 0
            };
            var i;
            var numTris = this.numTriangles * WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE;
            for (i = 0; i < numTris; i += WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE) {
                triNode.index = i;
                var result = rayCallback(null, triNode, ray, 0, upperBound);
                if (result) {
                    minimumResult = result;
                    upperBound = minimumResult.factor;
                }
            }

            return minimumResult;
        }
    };
    WebGLPhysicsPrivateTriangleArray.version = 1;
    return WebGLPhysicsPrivateTriangleArray;
})();

WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE = 17;

//
// WebGL Physics Convex Hull helpers.
// (Mostly mirrored with turbulenz/tools/mesh.py)
//
var WebGLPhysicsConvexHullHelpers = {
    isPlanar: function isPlanarFn(points) {
        // tolerance for distance from plane for a point
        // to be treat as coplanar.
        var tolerance = WebGLPhysicsConfig.COPLANAR_THRESHOLD;

        var p00 = points[0];
        var p01 = points[1];
        var p02 = points[2];

        // Find normal of plane from first 3 vertices.
        var e10 = (points[3] - p00);
        var e11 = (points[4] - p01);
        var e12 = (points[5] - p02);

        var e20 = (points[6] - p00);
        var e21 = (points[7] - p01);
        var e22 = (points[8] - p02);

        var n0 = (e11 * e22) - (e12 * e21);
        var n1 = (e12 * e20) - (e10 * e22);
        var n2 = (e10 * e21) - (e11 * e20);

        // Though normalisation isn't required to determine if point is 'on' the plane
        // We allow a distance tolerance so normalisation should be performed.
        var normalScale = 1 / Math.sqrt((n0 * n0) + (n1 * n1) + (n2 * n2));
        n0 *= normalScale;
        n1 *= normalScale;
        n2 *= normalScale;

        var planeDistance = -((p00 * n0) + (p01 * n1) + (p02 * n2));

        var i;
        var maxN = points.length;
        for (i = 0; i < maxN; i += 3) {
            var distance = (points[i] * n0) + (points[i + 1] * n1) + (points[i + 2] * n2) + planeDistance;
            if ((distance * distance) > tolerance) {
                return false;
            }
        }

        return true;
    },
    makePlanarConvexHull: function makePlanarConvexHullFn(points) {
        var DONT_NORMALIZE_THRESHOLD = 1e-6;

        // Use a 2D graham scan with projections of points onto their maximal plane.
        // Time complexity O(nh) for n points and h out-points.
        // Determine maximal plane for projection as the plane containing points.
        var p00 = points[0];
        var p01 = points[1];
        var p02 = points[2];

        var e10 = (points[3] - p00);
        var e11 = (points[4] - p01);
        var e12 = (points[5] - p02);

        var e20 = (points[6] - p00);
        var e21 = (points[7] - p01);
        var e22 = (points[8] - p02);

        // We do not require normalisation for projection onto plane.
        var normal0 = (e11 * e22) - (e12 * e21);
        var normal1 = (e12 * e20) - (e10 * e22);
        var normal2 = (e10 * e21) - (e11 * e20);

        // Determine tangent vectors.
        var tangent0, tangent1, tangent2;
        if ((normal0 * normal0) + (normal2 * normal2) < DONT_NORMALIZE_THRESHOLD) {
            tangent0 = 1;
            tangent1 = tangent2 = 0;
        } else {
            tangent0 = -normal2;
            tangent1 = 0;
            tangent2 = normal0;
        }
        var bitangent0 = (normal1 * tangent2) - (normal2 * tangent1);
        var bitangent1 = (normal2 * tangent0) - (normal0 * tangent2);
        var bitangent2 = (normal0 * tangent1) - (normal1 * tangent0);

        // Project points.
        var numPoints = points.length / 3;
        var projs = new Float32Array(numPoints * 2);
        var p0, p1, p2;
        var i;
        for (i = 0; i < numPoints; i += 1) {
            p0 = points[i * 3];
            p1 = points[(i * 3) + 1];
            p2 = points[(i * 3) + 2];

            projs[i * 2] = (p0 * tangent0) + (p1 * tangent1) + (p2 * tangent2);
            projs[(i * 2) + 1] = (p0 * bitangent0) + (p1 * bitangent1) + (p2 * bitangent2);
        }

        // Find first vertex on projected hull as minimal lexicographically.
        var i0 = 0;
        p00 = projs[0];
        p01 = projs[1];
        for (i = 2; i < (numPoints * 2); i += 2) {
            p0 = projs[i];
            p1 = projs[i + 1];
            if (p0 < p00 || (p0 === p00 && p1 < p01)) {
                i0 = (i / 2);
                p00 = p0;
                p01 = p1;
            }
        }

        // Perform graham scan.
        // hullVertices is a mapping for vertices used by hull from
        // their present indices to new indices in output mesh.
        var hullVertices = {};
        hullVertices[i0] = 0;
        var outVertexCount = 1;

        var hullTriangles = [];

        var fsti = i0;
        for (; ;) {
            var max0, max1, maxDistance;
            var i1 = -1;

            for (i = 0; i < (numPoints * 2); i += 2) {
                if (i === (i0 * 2)) {
                    continue;
                }

                p0 = projs[i];
                p1 = projs[i + 1];
                var plsq = (((p0 - p00) * (p0 - p00)) + ((p1 - p01) * (p1 - p01)));
                if (i1 === -1) {
                    i1 = (i / 2);
                    max0 = p0;
                    max1 = p1;
                    maxDistance = plsq;
                    continue;
                }

                // If this is not first vertex tested, determine if new vertex
                // makes a right turn looking in direction of edge, or is further
                // in same direction.
                var turn = ((max0 - p00) * (p1 - p01)) - ((max1 - p01) * (p0 - p00));
                if (turn < 0 || (turn === 0 && plsq > maxDistance)) {
                    i1 = (i / 2);
                    max0 = p0;
                    max1 = p1;
                    maxDistance = plsq;
                }
            }

            if (i1 in hullVertices) {
                break;
            }

            // Append vertex i1 to hull
            hullVertices[i1] = outVertexCount;
            outVertexCount += 1;

            if (i0 !== fsti) {
                hullTriangles.push(fsti);
                hullTriangles.push(i0);
                hullTriangles.push(i1);
            }

            i0 = i1;
            p00 = projs[i1 * 2];
            p01 = projs[(i1 * 2) + 1];
        }

        // Output triangle array!
        return this.createArray(points, hullTriangles, hullVertices, outVertexCount);
    },
    makeConvexHull: function makeConvexHullFn(points) {
        // 3D generalisation of Graham Scan to facilitate triangulation of the hull in generation
        // Time complexity O(nh) for n points, and h out-points.
        // Find first vertex on hull as minimal lexicographically ordered point.
        var i0 = 0;
        var p00 = points[0];
        var p01 = points[1];
        var p02 = points[2];

        var i;
        var p0, p1, p2;
        var numPoints = (points.length / 3);
        for (i = 3; i < (numPoints * 3); i += 3) {
            p0 = points[i];
            p1 = points[i + 1];
            p2 = points[i + 2];
            if (p0 < p00 || (p0 === p00 && (p1 < p01 || (p1 === p01 && p2 < p02)))) {
                i0 = (i / 3);
                p00 = p0;
                p01 = p1;
                p02 = p2;
            }
        }

        // Find second vertex on hull by performing 2D graham scan step on xy-plane projections of positions
        var i1 = -1;
        var cos1 = -2;
        var lsq1 = 0;
        var d0, d1;
        for (i = 0; i < (numPoints * 3); i += 3) {
            if (i === (i0 * 3)) {
                continue;
            }

            p0 = points[i];
            p1 = points[i + 1];
            d0 = p0 - p00;
            d1 = p1 - p01;
            var lsq = ((d0 * d0) + (d1 * d1));
            if (lsq === 0) {
                if (i1 === -1) {
                    i1 = (i / 3);
                }
                continue;
            }

            var cos = d1 / Math.sqrt(lsq);
            if (cos > cos1 || (cos === cos1 && lsq > lsq1)) {
                cos1 = cos;
                lsq1 = lsq;
                i1 = (i / 3);
            }
        }

        // Dictionary of visited edges to avoid duplicates
        // List of open edges to be visited by graham scan.
        var closedSet = {};
        var openSet = [i0, i1, i1, i0];

        // Dictionary of vertices used by hull as mapping from old to new indices in mesh.
        // And generated triangles
        var hullVertices = {};
        hullVertices[i0] = 0;
        hullVertices[i1] = 1;
        var outVertexCount = 2;

        var hullTriangles = [];

        while (openSet.length > 0) {
            // [ ...., i0, i1 ]
            i1 = openSet.pop();
            i0 = openSet.pop();

            if ((i0 + ":" + i1) in closedSet) {
                continue;
            }

            var i2 = -1;
            var maxEdge0, maxEdge1, maxEdge2;
            var maxDistance, maxProjection;

            p00 = points[i0 * 3];
            p01 = points[(i0 * 3) + 1];
            p02 = points[(i0 * 3) + 2];
            var edge0 = (points[i1 * 3] - p00);
            var edge1 = (points[(i1 * 3) + 1] - p01);
            var edge2 = (points[(i1 * 3) + 2] - p02);
            var isq = 1 / ((edge0 * edge0) + (edge1 * edge1) * (edge2 * edge2));

            for (i = 0; i < (numPoints * 3); i += 3) {
                if (i === (i0 * 3) || i === (i1 * 3)) {
                    continue;
                }

                p0 = points[i];
                p1 = points[i + 1];
                p2 = points[i + 2];

                // Find closest point on line containing the edge to determine vector to p
                // perpendicular to edge. This is not necessary for computing the turn
                // since the value of 'turn' computed is actually the same whether we do
                // this or not, however it is needed to be able to sort equal turn vertices
                // by distance.
                var t = (((p0 - p00) * edge0) + ((p1 - p01) * edge1) + ((p2 - p02) * edge2)) * isq;
                var pEdge0 = (p0 - (p00 + (edge0 * t)));
                var pEdge1 = (p1 - (p01 + (edge1 * t)));
                var pEdge2 = (p2 - (p02 + (edge2 * t)));

                // Ignore vertex if |pedge| = 0, thus ignoring vertices on edge itself
                // and so avoiding generating degenerate triangles.
                var plsq = ((pEdge0 * pEdge0) + (pEdge1 * pEdge1) + (pEdge2 * pEdge2));
                if (plsq <= WebGLPhysicsConfig.COLLINEAR_THRESHOLD) {
                    continue;
                }

                if (i2 === -1) {
                    i2 = (i / 3);
                    maxEdge0 = pEdge0;
                    maxEdge1 = pEdge1;
                    maxEdge2 = pEdge2;
                    maxDistance = plsq;
                    maxProjection = t;
                    continue;
                }

                // If this is not the first vertex tested, determine if new vertex
                // is a right turn looking in direction of edge, or is further in
                // same direction
                //
                // We require a special case when pedge, and maxedge are coplanar
                // with edge as the computed turn will be 0 and we must check
                // if the cross product is facing into the hull or outside to
                // determine left/right instead.
                var axis0 = ((pEdge1 * maxEdge2) - (pEdge2 * maxEdge1));
                var axis1 = ((pEdge2 * maxEdge0) - (pEdge0 * maxEdge2));
                var axis2 = ((pEdge0 * maxEdge1) - (pEdge1 * maxEdge0));

                var coplanar = (pEdge0 * ((edge1 * maxEdge2) - (edge2 * maxEdge1)) + pEdge1 * ((edge2 * maxEdge0) - (edge0 * maxEdge2)) + pEdge2 * ((edge0 * maxEdge1) - (edge1 * maxEdge0)));
                if ((coplanar * coplanar) < WebGLPhysicsConfig.COPLANAR_THRESHOLD) {
                    if (((pEdge0 * maxEdge0) + (pEdge1 * maxEdge1) + (pEdge2 * maxEdge2)) >= 0) {
                        if (plsq > maxDistance || (plsq === maxDistance && t > maxProjection)) {
                            i2 = (i / 3);
                            maxEdge0 = pEdge0;
                            maxEdge1 = pEdge1;
                            maxEdge2 = pEdge2;
                            maxDistance = plsq;
                            maxProjection = t;
                        }
                    } else {
                        d0 = (p0 - p00);
                        d1 = (p1 - p01);
                        var d2 = (p2 - p02);
                        axis0 = ((d1 * edge2) - (d2 * edge1));
                        axis1 = ((d2 * edge0) - (d0 * edge2));
                        axis2 = ((d0 * edge1) - (d1 * edge0));

                        // Determine if axis points into, or out of the convex hull.
                        var internal = true;
                        var j;
                        for (j = 0; j < (numPoints * 3); j += 3) {
                            if (((axis0 * (points[j] - p00)) + (axis1 * (points[j + 1] - p01)) + (axis2 * (points[j + 2] - p02))) < 0) {
                                internal = false;
                                break;
                            }
                        }

                        if (internal) {
                            i2 = (i / 3);
                            maxEdge0 = pEdge0;
                            maxEdge1 = pEdge1;
                            maxEdge2 = pEdge2;
                            maxDistance = plsq;
                            maxProjection = t;
                        }
                    }
                } else {
                    var turn = (axis0 * edge0) + (axis1 * edge1) + (axis2 * edge2);
                    if (turn < 0 || (turn <= WebGLPhysicsConfig.COLLINEAR_THRESHOLD && plsq > maxDistance)) {
                        i2 = (i / 3);
                        maxEdge0 = pEdge0;
                        maxEdge1 = pEdge1;
                        maxEdge2 = pEdge2;
                        maxDistance = plsq;
                        maxProjection = t;
                    }
                }
            }

            if (!(i2 in hullVertices)) {
                hullVertices[i2] = outVertexCount;
                outVertexCount += 1;
            }

            if (!((i0 + ":" + i1) in closedSet || (i1 + ":" + i2) in closedSet || (i2 + ":" + i0) in closedSet)) {
                hullTriangles.push(i0);
                hullTriangles.push(i1);
                hullTriangles.push(i2);

                closedSet[i0 + ":" + i1] = true;
                closedSet[i1 + ":" + i2] = true;
                closedSet[i2 + ":" + i0] = true;

                openSet.push(i2);
                openSet.push(i1);
                openSet.push(i0);
                openSet.push(i2);
            }
        }

        // Output triangle array!
        return this.createArray(points, hullTriangles, hullVertices, outVertexCount);
    },
    createArray: function createArrayFn(points, indices, mapping, vertexCount) {
        // Port removeRedundantVertices from mesh.py with extra param to specify used vertices.
        // Modified to create a WebGLPhysicsPrivateTriangleArray
        var outPoints = new Float32Array(vertexCount * 3);
        var triangleCount = indices.length;
        var outIndices = (vertexCount < 65536 ? new Uint16Array(triangleCount) : new Uint32Array(triangleCount));

        // Produce outPoints array
        var numPoints = (points.length / 3);
        var i;
        for (i = 0; i < numPoints; i += 1) {
            if (!(i in mapping)) {
                continue;
            }

            var newIndex = (mapping[i] * 3);
            outPoints[newIndex] = points[i * 3];
            outPoints[newIndex + 1] = points[(i * 3) + 1];
            outPoints[newIndex + 2] = points[(i * 3) + 2];
        }

        for (i = 0; i < triangleCount; i += 1) {
            outIndices[i] = mapping[indices[i]];
        }

        return WebGLPhysicsTriangleArray.create({
            vertices: outPoints,
            indices: outIndices,
            dontCopy: true
        })._private;
    }
};

//
// WebGLPhysicsTriangleMeshShape
//
var WebGLPhysicsTriangleMeshShape = (function () {
    function WebGLPhysicsTriangleMeshShape() {
    }
    WebGLPhysicsTriangleMeshShape.prototype.rayTest = function (ray) {
        return this.triangleArray.rayTest(ray);
    };

    WebGLPhysicsTriangleMeshShape.create = function (params) {
        var rett = new WebGLPhysicsShape();
        var t = new WebGLPhysicsTriangleMeshShape();
        rett._private = t;
        t._public = rett;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var triangleArray = params.triangleArray._private;

        var extents = triangleArray.extents;
        var e0 = extents[0];
        var e1 = extents[1];
        var e2 = extents[2];
        var e3 = extents[3];
        var e4 = extents[4];
        var e5 = extents[5];

        var h0 = ((0.5 * (e3 - e0)) + margin);
        var h1 = ((0.5 * (e4 - e1)) + margin);
        var h2 = ((0.5 * (e5 - e2)) + margin);
        var c0 = (0.5 * (e0 + e3));
        var c1 = (0.5 * (e1 + e4));
        var c2 = (0.5 * (e2 + e5));

        var buffer = new Float32Array(6);

        t.triangleArray = triangleArray;
        t.radius = Math.sqrt((h0 * h0) + (h1 * h1) + (h2 * h2));
        t.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        if (c0 !== 0 || c1 !== 0 || c2 !== 0) {
            t.center = VMath.v3Build(c0, c1, c2);
        } else {
            t.center = undefined;
        }
        t.inertia = VMath.v3Build(0, 0, 0, buffer.subarray(3, 6));
        t.collisionRadius = margin;

        initShapeProperties(rett, "TRIANGLE_MESH");

        Object.defineProperty(rett, "triangleArray", {
            get: function shapeGetTriangleArray() {
                return this._private.triangleArray;
            },
            enumerable: true
        });

        return rett;
    };
    WebGLPhysicsTriangleMeshShape.version = 1;
    return WebGLPhysicsTriangleMeshShape;
})();

WebGLPhysicsTriangleMeshShape.prototype.type = "TRIANGLE_MESH";

//
// WebGL Physics Convex Hull Shape
//
var WebGLPhysicsConvexHullShape = (function () {
    function WebGLPhysicsConvexHullShape() {
    }
    WebGLPhysicsConvexHullShape.prototype.rayTest = function (ray) {
        var triangleArray = this.triangleArray;
        if (triangleArray === undefined) {
            return null;
        }

        return triangleArray.rayTest(ray);
    };

    WebGLPhysicsConvexHullShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        var v0 = vec[0];
        var v1 = vec[1];
        var v2 = vec[2];

        var topology = this.supportTopology;
        var points = this.triangleArray.vertices;
        if (this.lastSupport === undefined) {
            this.lastSupport = 0;
        }

        // Start search at last support point.
        var maxv = this.lastSupport;
        var ind = topology[maxv];
        var max = (points[ind] * v0) + (points[ind + 1] * v1) + (points[ind + 2] * v2);

        for (; ;) {
            // Determine if a vertex linked in topology is a better support point.
            var next = -1;
            var n;
            var maxN = topology[maxv + 1];
            for (n = 0; n < maxN; n += 1) {
                var v = topology[maxv + 2 + n];
                ind = topology[v];
                var vdot = (points[ind] * v0) + (points[ind + 1] * v1) + (points[ind + 2] * v2);
                if (vdot > max) {
                    max = vdot;
                    next = v;
                }
            }

            if (next !== -1) {
                maxv = next;
                continue;
            } else {
                break;
            }
        }

        // Cache maximum support to seed next call to method.
        this.lastSupport = maxv;

        ind = topology[maxv];
        dst[0] = points[ind];
        dst[1] = points[ind + 1];
        dst[2] = points[ind + 2];
    };

    WebGLPhysicsConvexHullShape.create = function (params) {
        var retc = new WebGLPhysicsShape();
        var c = new WebGLPhysicsConvexHullShape();
        retc._private = c;
        c._public = retc;

        var margin = (params.margin !== undefined) ? params.margin : 0.04;
        var points = params.points;

        var minExtent = params.minExtent;
        var maxExtent = params.maxExtent;

        var min0, min1, min2, max0, max1, max2;

        if (!minExtent || !maxExtent) {
            min0 = points[0];
            min1 = points[1];
            min2 = points[2];
            max0 = min0;
            max1 = min1;
            max2 = min2;
            var maxN = points.length;
            var n;
            var v0, v1, v2;
            for (n = 3; n < maxN; n += 3) {
                v0 = points[n];
                v1 = points[n + 1];
                v2 = points[n + 2];
                if (min0 > v0) {
                    min0 = v0;
                } else if (max0 < v0) {
                    max0 = v0;
                }
                if (min1 > v1) {
                    min1 = v1;
                } else if (max1 < v1) {
                    max1 = v1;
                }
                if (min2 > v2) {
                    min2 = v2;
                } else if (max2 < v2) {
                    max2 = v2;
                }
            }
        } else {
            min0 = minExtent[0];
            min1 = minExtent[1];
            min2 = minExtent[2];
            max0 = maxExtent[0];
            max1 = maxExtent[1];
            max2 = maxExtent[2];
        }

        var h0 = ((0.5 * (max0 - min0)) + margin);
        var h1 = ((0.5 * (max1 - min1)) + margin);
        var h2 = ((0.5 * (max2 - min2)) + margin);
        var c0 = (0.5 * (min0 + max0));
        var c1 = (0.5 * (min1 + max1));
        var c2 = (0.5 * (min2 + max2));

        var lx = (2.0 * h0);
        var ly = (2.0 * h1);
        var lz = (2.0 * h2);
        lx *= lx;
        ly *= ly;
        lz *= lz;

        var massRatio = (1.0 / 12.0);

        var buffer = new Float32Array(6);

        c.points = new Float32Array(points);
        c.radius = Math.sqrt((h0 * h0) + (h1 * h1) + (h2 * h2));
        c.halfExtents = VMath.v3Build(h0, h1, h2, buffer.subarray(0, 3));
        if (c0 !== 0 || c1 !== 0 || c2 !== 0) {
            c.center = VMath.v3Build(c0, c1, c2);
        } else {
            c.center = undefined;
        }
        c.inertia = VMath.v3Build(massRatio * (ly + lz), massRatio * (lx + lz), massRatio * (lx + ly), buffer.subarray(3, 6));
        c.collisionRadius = margin;

        if (points.length < 9) {
            throw "At present time, WebGL PhysicsDevice does not permit a convex hull to contain " + "less than 3 vertices";
        } else {
            var planar = WebGLPhysicsConvexHullHelpers.isPlanar(points);
            if (planar) {
                c.triangleArray = WebGLPhysicsConvexHullHelpers.makePlanarConvexHull(points);
            } else {
                c.triangleArray = WebGLPhysicsConvexHullHelpers.makeConvexHull(points);
            }

            // Produce edge topology for faster support search.  Each
            // vertex keeps a reference to each neighbouring vertex on
            // triangulated surface.
            //
            // Experiment showed that even for a planar convex hull of
            // 3 vertices, we only search on average 1.6 vertices
            // instead of all 3 so is better even for this smallest
            // case.
            var supportTopology = [];

            points = c.triangleArray.vertices;
            maxN = points.length;
            for (n = 0; n < maxN; n += 3) {
                supportTopology[n / 3] = [];
            }

            var m;
            if (planar) {
                for (n = 0; n < (maxN / 3); n += 1) {
                    m = (n + 1) % (maxN / 3);
                    supportTopology[n].push(m);
                    supportTopology[m].push(n);
                }
            } else {
                var triangles = c.triangleArray.indices;
                maxN = triangles.length;
                for (n = 0; n < maxN; n += 3) {
                    var i0 = triangles[n];
                    var i1 = triangles[n + 1];
                    var i2 = triangles[n + 2];

                    // Create links i0 -> i1 -> i2 -> i0.
                    // Adjacent triangles will take care of back links.
                    supportTopology[i0].push(i1);
                    supportTopology[i1].push(i2);
                    supportTopology[i2].push(i0);
                }
            }

            // Additionally each vertex keeps a reference to the vertex on far side of hull.
            // Tests show that this reduces the number of vertices searched in support function.
            //
            // Planar case, only do this for 6 vertices or more, or else includes uneeded edges.
            // Non-planar case, do this for 10 vertices or more. Experiment showed this to be a good
            // threshold.
            maxN = points.length;
            if ((planar && maxN >= (3 * 6)) || (!planar && maxN >= (3 * 10))) {
                for (n = 0; n < maxN; n += 3) {
                    var min = Number.MAX_VALUE;
                    v0 = points[n];
                    v1 = points[n + 1];
                    v2 = points[n + 2];

                    var minm;
                    for (m = 0; m < maxN; m += 3) {
                        var dot = (v0 * points[m]) + (v1 * points[m + 1]) + (v2 * points[m + 2]);
                        if (dot < min) {
                            min = dot;
                            minm = m;
                        }
                    }

                    supportTopology[n / 3].push(minm / 3);
                }
            }

            // Flatten supportTopology array of arrays, into a single typed array.
            //
            // We take topology array like: [[1,2],[0],[0,1,3],[0,1,2]]
            // Decorate with index of vertex in triangle array
            // and number of edges: [[0,2|1,2], [3,1|0], [6,3|0,1,3], [9,3|0,1,2]]
            // And then flatten into array: [0,2,4,7, 3,1,0, 6,3,0,4,12, 9,3,0,4,7]
            //
            // Compute size of array, and positions of vertex data.
            var mapping = [];
            var size = 0;
            for (n = 0; n < (maxN / 3); n += 1) {
                mapping.push(size);
                size += supportTopology[n].length + 2;
            }

            // Produce flattened array.
            c.supportTopology = (size > 65536) ? new Uint32Array(size) : new Uint16Array(size);
            var index = 0;
            for (n = 0; n < (maxN / 3); n += 1) {
                c.supportTopology[index] = (n * 3);
                index += 1;

                var topology = supportTopology[n];
                c.supportTopology[index] = topology.length;
                index += 1;

                for (m = 0; m < topology.length; m += 1) {
                    c.supportTopology[index] = mapping[topology[m]];
                    index += 1;
                }
            }
        }

        initShapeProperties(retc, "CONVEX_HULL");
        return retc;
    };
    WebGLPhysicsConvexHullShape.version = 1;
    return WebGLPhysicsConvexHullShape;
})();

WebGLPhysicsConvexHullShape.prototype.type = "CONVEX_HULL";

//
// WebGLPhysicsPrivateBody
//
var WebGLPhysicsPrivateBody = (function () {
    function WebGLPhysicsPrivateBody(params, publicObject) {
        this._public = publicObject;

        this.id = WebGLPhysicsPrivateBody.uniqueId;
        WebGLPhysicsPrivateBody.uniqueId += 1;

        this.world = null;
        this.shape = params.shape._private;

        this.friction = (params.friction !== undefined) ? params.friction : 0.5;
        this.restitution = (params.restitution !== undefined) ? params.restitution : 0.0;

        var buffer = new Float32Array(12 + 12 + 6 + 12 + 12 + 12 + 12);
        var bufferIndex = 0;

        var xform = params.transform;
        this.transform = (xform ? VMath.m43Copy(xform, buffer.subarray(bufferIndex, (bufferIndex + 12))) : VMath.m43BuildIdentity(buffer.subarray(bufferIndex, (bufferIndex + 12))));
        bufferIndex += 12;

        this.arbiters = [];

        // Tracks constraints that are inside of a space, and making use of this object.
        // We only track these constraints to avoid GC issues.
        this.constraints = [];

        // [v0, v1, v2]
        // [w0, w1, w2]
        // [v0, v1, v2] <-- bias velocity
        // [w0, w1, w2] <-- bias velocity
        this.velocity = buffer.subarray(bufferIndex, (bufferIndex + 12));
        bufferIndex += 12;

        var vel = params.linearVelocity;
        if (vel) {
            this.velocity[0] = vel[0];
            this.velocity[1] = vel[1];
            this.velocity[2] = vel[2];
        }
        vel = params.angularVelocity;
        if (vel) {
            this.velocity[3] = vel[0];
            this.velocity[4] = vel[1];
            this.velocity[5] = vel[2];
        }

        this.linearDamping = (params.linearDamping !== undefined) ? params.linearDamping : 0.0;
        this.angularDamping = (params.angularDamping !== undefined) ? params.angularDamping : 0.0;

        this.extents = buffer.subarray(bufferIndex, (bufferIndex + 6));
        bufferIndex += 6;

        // For continous collision detection
        this.startTransform = VMath.m43BuildIdentity(buffer.subarray(bufferIndex, (bufferIndex + 12)));
        bufferIndex += 12;

        this.endTransform = VMath.m43BuildIdentity(buffer.subarray(bufferIndex, (bufferIndex + 12)));
        bufferIndex += 12;

        // For kinematic objects.
        this.prevTransform = VMath.m43Copy(this.transform, buffer.subarray(bufferIndex, (bufferIndex + 12)));
        bufferIndex += 12;

        this.newTransform = VMath.m43BuildIdentity(buffer.subarray(bufferIndex, (bufferIndex + 12)));
        bufferIndex += 12;

        this.island = null;
        this.islandRoot = this;
        this.islandRank = 0;

        // used for kinematics so that it is kept alive for a single
        // step before being sweffed.
        this.delaySleep = true;

        this.group = 0;
        this.mask = 0;
        this.kinematic = false;
        this.fixedRotation = false;
        this.mass = 0;
        this.inverseMass = 0;
        this.inverseInertiaLocal = null;
        this.inverseInertia = null;
        this.collisionObject = false;
        this.permitSleep = false;
        this.sweepFrozen = false;
        this.active = false;
        this.contactCallbacks = null;
    }
    // Used for kinematics.
    // TODO: Should be used for convexSweep to permit non-linear sweeps.
    WebGLPhysicsPrivateBody.prototype.computeDeltaVelocity = function (timeStep, from, to, inputVelocity) {
        var velocity = inputVelocity || this.velocity;

        var active = false;

        velocity[0] = (to[9] - from[9]);
        velocity[1] = (to[10] - from[10]);
        velocity[2] = (to[11] - from[11]);
        if (velocity[0] !== 0 || velocity[1] !== 0 || velocity[2] !== 0) {
            active = true;
        }

        // do this afterwards so that active is true, even if timeStep is 0
        // for non-equal position transforms.
        velocity[0] /= timeStep;
        velocity[1] /= timeStep;
        velocity[2] /= timeStep;

        //var deltaRotation = VMath.m33Mul(VMath.m33Inverse(from), to);
        var m0 = (from[0] * to[0]) + (from[3] * to[3]) + (from[6] * to[6]);
        var m1 = (from[0] * to[1]) + (from[3] * to[4]) + (from[6] * to[7]);
        var m2 = (from[0] * to[2]) + (from[3] * to[5]) + (from[6] * to[8]);
        var m3 = (from[1] * to[0]) + (from[4] * to[3]) + (from[7] * to[6]);
        var m4 = (from[1] * to[1]) + (from[4] * to[4]) + (from[7] * to[7]);
        var m5 = (from[1] * to[2]) + (from[4] * to[5]) + (from[7] * to[8]);
        var m6 = (from[2] * to[0]) + (from[5] * to[3]) + (from[8] * to[6]);
        var m7 = (from[2] * to[1]) + (from[5] * to[4]) + (from[8] * to[7]);
        var m8 = (from[2] * to[2]) + (from[5] * to[5]) + (from[8] * to[8]);

        //var quat = VMath.quatFromM43(deltaRotation);
        var x, y, z, w, s;
        var trace = m0 + m4 + m8 + 1;
        if (trace > VMath.precision) {
            w = Math.sqrt(trace) / 2;
            x = (m5 - m7) / (4 * w);
            y = (m6 - m2) / (4 * w);
            z = (m1 - m3) / (4 * w);
        } else {
            if ((m0 > m4) && (m0 > m8)) {
                s = Math.sqrt(1.0 + m0 - m4 - m8) * 2;
                w = (m5 - m7) / s;
                x = 0.25 * s;
                y = (m3 + m1) / s;
                z = (m6 + m2) / s;
            } else if (m4 > m8) {
                s = Math.sqrt(1.0 + m4 - m0 - m8) * 2;
                w = (m6 - m2) / s;
                x = (m3 + m1) / s;
                y = 0.25 * s;
                z = (m7 + m5) / s;
            } else {
                s = Math.sqrt(1.0 + m8 - m0 - m4) * 2;
                w = (m1 - m3) / s;
                x = (m6 + m2) / s;
                y = (m7 + m5) / s;
                z = 0.25 * s;
            }
        }

        var angle = Math.acos(w) * 2.0;
        var sin_sqrd = 1.0 - (w * w);
        if (sin_sqrd < VMath.precision || angle === 0) {
            velocity[3] = velocity[4] = velocity[5] = 0;
        } else {
            var scale = angle / (timeStep * Math.sqrt(sin_sqrd));
            velocity[3] = x * scale;
            velocity[4] = y * scale;
            velocity[5] = z * scale;
            active = true;
        }

        return active;
    };

    // Used for kinematic and dynamics.
    WebGLPhysicsPrivateBody.prototype.calculateSweptExtents = function (extents) {
        var shape = this.shape;
        var radius = shape.radius;

        var startTransform = this.startTransform;
        var x0 = startTransform[9];
        var x1 = startTransform[10];
        var x2 = startTransform[11];

        var transform = this.transform;
        var y0 = transform[9];
        var y1 = transform[10];
        var y2 = transform[11];

        var tmp;
        if (x0 > y0) {
            tmp = x0;
            x0 = y0;
            y0 = tmp;
        }
        if (x1 > y1) {
            tmp = x1;
            x1 = y1;
            y1 = tmp;
        }
        if (x2 > y2) {
            tmp = x2;
            x2 = y2;
            y2 = tmp;
        }

        extents[0] = x0 - radius;
        extents[1] = x1 - radius;
        extents[2] = x2 - radius;
        extents[3] = y0 + radius;
        extents[4] = y1 + radius;
        extents[5] = y2 + radius;
    };

    // use for all types.
    WebGLPhysicsPrivateBody.prototype.calculateExtents = function (extents) {
        var shape = this.shape;
        var center = shape.center;
        var halfExtents = shape.halfExtents;
        var h0 = halfExtents[0];
        var h1 = halfExtents[1];
        var h2 = halfExtents[2];

        var transform = this.transform;
        var m0 = transform[0];
        var m1 = transform[1];
        var m2 = transform[2];
        var m3 = transform[3];
        var m4 = transform[4];
        var m5 = transform[5];
        var m6 = transform[6];
        var m7 = transform[7];
        var m8 = transform[8];

        var ct0 = transform[9];
        var ct1 = transform[10];
        var ct2 = transform[11];
        if (center) {
            var c0 = center[0];
            var c1 = center[1];
            var c2 = center[2];

            if (c0 !== 0 || c1 !== 0 || c2 !== 0) {
                ct0 += (m0 * c0 + m3 * c1 + m6 * c2);
                ct1 += (m1 * c0 + m4 * c1 + m7 * c2);
                ct2 += (m2 * c0 + m5 * c1 + m8 * c2);
            }
        }

        // fails when h0, h1, h2 are infinite, as JS has 0 * infinity = NaN not 0!!!!
        //var ht0 = ((m0 < 0 ? -m0 : m0) * h0 + (m3 < 0 ? -m3 : m3) * h1 + (m6 < 0 ? -m6 : m6) * h2);
        //var ht1 = ((m1 < 0 ? -m1 : m1) * h0 + (m4 < 0 ? -m4 : m4) * h1 + (m7 < 0 ? -m7 : m7) * h2);
        //var ht2 = ((m2 < 0 ? -m2 : m2) * h0 + (m5 < 0 ? -m5 : m5) * h1 + (m8 < 0 ? -m8 : m8) * h2);
        var ht0 = ((m0 < 0 ? -m0 * h0 : m0 > 0 ? m0 * h0 : 0) + (m3 < 0 ? -m3 * h1 : m3 > 0 ? m3 * h1 : 0) + (m6 < 0 ? -m6 * h2 : m6 > 0 ? m6 * h2 : 0));
        var ht1 = ((m1 < 0 ? -m1 * h0 : m1 > 0 ? m1 * h0 : 0) + (m4 < 0 ? -m4 * h1 : m4 > 0 ? m4 * h1 : 0) + (m7 < 0 ? -m7 * h2 : m7 > 0 ? m7 * h2 : 0));
        var ht2 = ((m2 < 0 ? -m2 * h0 : m2 > 0 ? m2 * h0 : 0) + (m5 < 0 ? -m5 * h1 : m5 > 0 ? m5 * h1 : 0) + (m8 < 0 ? -m8 * h2 : m8 > 0 ? m8 * h2 : 0));

        extents[0] = (ct0 - ht0);
        extents[1] = (ct1 - ht1);
        extents[2] = (ct2 - ht2);
        extents[3] = (ct0 + ht0);
        extents[4] = (ct1 + ht1);
        extents[5] = (ct2 + ht2);
    };

    // used for all types.
    WebGLPhysicsPrivateBody.prototype.rayTest = function (ray) {
        //Transform ray; assuming transform is orthogonal
        var transform = this.transform;
        var rayT = {
            origin: WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformPoint(transform, ray.origin),
            direction: WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformVector(transform, ray.direction),
            maxFactor: ray.maxFactor
        };

        var result = this.shape.rayTest(rayT);
        if (result !== null) {
            result.hitPoint = VMath.m43TransformPoint(transform, result.hitPoint, result.hitPoint);
            result.hitNormal = VMath.m43TransformVector(transform, result.hitNormal, result.hitNormal);
        }

        return result;
    };

    // used for kinematics and dynamics
    WebGLPhysicsPrivateBody.prototype.integratePositionWithVelocities = function (transform, outTransform, timeStep, offset) {
        var velocity = this.velocity;
        var sqrt = Math.sqrt;

        // x += h * v
        outTransform[9] = transform[9] + (timeStep * velocity[offset]);
        outTransform[10] = transform[10] + (timeStep * velocity[offset + 1]);
        outTransform[11] = transform[11] + (timeStep * velocity[offset + 2]);

        // A += h * skew(w) * A
        var w0 = velocity[offset + 3] * timeStep;
        var w1 = velocity[offset + 4] * timeStep;
        var w2 = velocity[offset + 5] * timeStep;

        var A0 = transform[0];
        var A1 = transform[1];
        var A2 = transform[2];
        var A3 = transform[3];
        var A4 = transform[4];
        var A5 = transform[5];
        var A6 = transform[6];
        var A7 = transform[7];
        var A8 = transform[8];

        var B0 = A0 - (w2 * A1) + (w1 * A2);
        var B1 = A1 + (w2 * A0) - (w0 * A2);
        var B2 = A2 - (w1 * A0) + (w0 * A1);
        var B3 = A3 - (w2 * A4) + (w1 * A5);
        var B4 = A4 + (w2 * A3) - (w0 * A5);
        var B5 = A5 - (w1 * A3) + (w0 * A4);
        var B6 = A6 - (w2 * A7) + (w1 * A8);
        var B7 = A7 + (w2 * A6) - (w0 * A8);
        var B8 = A8 - (w1 * A6) + (w0 * A7);

        // Orthornormalize with modified gram schmidt.
        var scale = 1 / sqrt((B0 * B0) + (B1 * B1) + (B2 * B2));
        B0 *= scale;
        B1 *= scale;
        B2 *= scale;

        scale = -((B0 * B3) + (B1 * B4) + (B2 * B5));
        B3 += B0 * scale;
        B4 += B1 * scale;
        B5 += B2 * scale;

        scale = 1 / sqrt((B3 * B3) + (B4 * B4) + (B5 * B5));
        B3 *= scale;
        B4 *= scale;
        B5 *= scale;

        scale = -((B0 * B6) + (B1 * B7) + (B2 * B8));
        B6 += B0 * scale;
        B7 += B1 * scale;
        B8 += B2 * scale;

        scale = -((B3 * B6) + (B4 * B7) + (B5 * B8));
        B6 += B3 * scale;
        B7 += B4 * scale;
        B8 += B5 * scale;

        scale = 1 / sqrt((B6 * B6) + (B7 * B7) + (B8 * B8));
        B6 *= scale;
        B7 *= scale;
        B8 *= scale;

        outTransform[0] = B0;
        outTransform[1] = B1;
        outTransform[2] = B2;
        outTransform[3] = B3;
        outTransform[4] = B4;
        outTransform[5] = B5;
        outTransform[6] = B6;
        outTransform[7] = B7;
        outTransform[8] = B8;
    };

    // used for dynamics.
    WebGLPhysicsPrivateBody.prototype.applyBiasVelocities = function (timeStep) {
        var velocity = this.velocity;
        this.integratePositionWithVelocities(this.transform, this.startTransform, timeStep, 6);

        // Set bias velocities back to 0.
        velocity[6] = velocity[7] = velocity[8] = 0;
        velocity[9] = velocity[10] = velocity[11] = 0;
    };

    // used for kinematics and dynamics.
    WebGLPhysicsPrivateBody.prototype.integratePosition = function (timeStep) {
        this.integratePositionWithVelocities(this.startTransform, this.transform, timeStep, 0);
    };

    // used for dynamics.
    WebGLPhysicsPrivateBody.prototype.refreshInertiaTensor = function () {
        var A = this.transform;
        var inertia = this.inverseInertiaLocal;
        var i0 = inertia[0];
        var i1 = inertia[1];
        var i2 = inertia[2];

        var A0 = A[0];
        var A1 = A[1];
        var A2 = A[2];
        var A3 = A[3];
        var A4 = A[4];
        var A5 = A[5];
        var A6 = A[6];
        var A7 = A[7];
        var A8 = A[8];

        // I = A * 1/I' * A^T
        var I = this.inverseInertia;
        I[0] = (A0 * A0 * i0) + (A3 * A3 * i1) + (A6 * A6 * i2);
        I[1] = (A0 * A1 * i0) + (A3 * A4 * i1) + (A6 * A7 * i2);
        I[2] = (A0 * A2 * i0) + (A3 * A5 * i1) + (A6 * A8 * i2);
        I[3] = (A1 * A0 * i0) + (A4 * A3 * i1) + (A7 * A6 * i2);
        I[4] = (A1 * A1 * i0) + (A4 * A4 * i1) + (A7 * A7 * i2);
        I[5] = (A1 * A2 * i0) + (A4 * A5 * i1) + (A7 * A8 * i2);
        I[6] = (A2 * A0 * i0) + (A5 * A3 * i1) + (A8 * A6 * i2);
        I[7] = (A2 * A1 * i0) + (A5 * A4 * i1) + (A8 * A7 * i2);
        I[8] = (A2 * A2 * i0) + (A5 * A5 * i1) + (A8 * A8 * i2);
    };

    // used for dynamics.
    WebGLPhysicsPrivateBody.prototype.integrateVelocity = function (gravity, timeStep) {
        var velocity = this.velocity;

        var pow = Math.pow;

        // v += h * F / m. Damping applied directly.
        var linDrag = pow(1 - this.linearDamping, timeStep);
        velocity[0] = (velocity[0] + (timeStep * gravity[0])) * linDrag;
        velocity[1] = (velocity[1] + (timeStep * gravity[1])) * linDrag;
        velocity[2] = (velocity[2] + (timeStep * gravity[2])) * linDrag;

        var angDrag = pow(1 - this.angularDamping, timeStep);
        var w0 = velocity[3] * angDrag;
        var w1 = velocity[4] * angDrag;
        var w2 = velocity[5] * angDrag;

        // Apply clamping to angularVelocity
        var max_angular = WebGLPhysicsConfig.MAX_ANGULAR / timeStep;
        var wlsq = ((w0 * w0) + (w1 * w1) + (w2 * w2));
        if (wlsq > (max_angular * max_angular)) {
            var scale = max_angular / Math.sqrt(wlsq);
            w0 *= scale;
            w1 *= scale;
            w2 *= scale;
        }

        velocity[3] = w0;
        velocity[4] = w1;
        velocity[5] = w2;
    };

    // Return false if body is (considering purely velocity) able to sleep.
    // used for dynamics.
    WebGLPhysicsPrivateBody.prototype.isActiveVelocity = function (linear, angular) {
        var r = this.shape.radius;

        var velocity = this.velocity;
        var v0 = velocity[0];
        var v1 = velocity[1];
        var v2 = velocity[2];
        var vmag = ((v0 * v0) + (v1 * v1) + (v2 * v2));
        if (vmag > (linear * r * r)) {
            return true;
        }

        v0 = velocity[3];
        v1 = velocity[4];
        v2 = velocity[5];
        if (((v0 * v0) + (v1 * v1) + (v2 * v2)) > angular) {
            return true;
        }

        return false;
    };

    // Return false if body is (taking into account sleep delay) able to sleep.
    // used for dynamics.
    WebGLPhysicsPrivateBody.prototype.isActive = function (/* timeStep */ ) {
        if (!this.permitSleep) {
            return true;
        }

        if (this.isActiveVelocity(WebGLPhysicsConfig.SLEEP_LINEAR_SQ, WebGLPhysicsConfig.SLEEP_ANGULAR_SQ)) {
            this.wakeTimeStamp = this.world.timeStamp;
            return true;
        }

        return ((this.wakeTimeStamp + WebGLPhysicsConfig.SLEEP_DELAY) > this.world.timeStamp);
    };
    WebGLPhysicsPrivateBody.version = 1;

    WebGLPhysicsPrivateBody.uniqueId = 0;
    return WebGLPhysicsPrivateBody;
})();

//
// WebGL Physics Collision Object
//
var WebGLPhysicsCollisionObject = (function () {
    function WebGLPhysicsCollisionObject() {
    }
    WebGLPhysicsCollisionObject.prototype.calculateExtents = function (extents) {
        this._private.calculateExtents(extents);
    };

    WebGLPhysicsCollisionObject.prototype.calculateTransform = function (transform, origin) {
        var privateTransform = this._private.transform;
        if (origin) {
            VMath.m43NegOffset(privateTransform, origin, transform);
        } else {
            transform[0] = privateTransform[0];
            transform[1] = privateTransform[1];
            transform[2] = privateTransform[2];
            transform[3] = privateTransform[3];
            transform[4] = privateTransform[4];
            transform[5] = privateTransform[5];
            transform[6] = privateTransform[6];
            transform[7] = privateTransform[7];
            transform[8] = privateTransform[8];
            transform[9] = privateTransform[9];
            transform[10] = privateTransform[10];
            transform[11] = privateTransform[11];
        }
    };

    WebGLPhysicsCollisionObject.prototype.clone = function () {
        return WebGLPhysicsCollisionObject.create(this);
    };

    WebGLPhysicsCollisionObject.create = function (params) {
        var rets = new WebGLPhysicsCollisionObject();
        var s = new WebGLPhysicsPrivateBody(params, rets);
        rets._private = s;

        //read/write, no side effects
        rets.userData = ("userData" in params) ? params.userData : null;

        // read only, no getter needed
        Object.defineProperty(rets, "shape", {
            value: params.shape,
            enumerable: true
        });

        var kinematic = (params.kinematic !== undefined) ? params.kinematic : false;

        // read/write, side effects
        Object.defineProperty(rets, "transform", {
            get: function collisionObjectGetTransform() {
                return VMath.m43Copy(this._private.transform);
            },
            set: function collisionObjectSetTransform(transform) {
                var pr = this._private;

                if (pr.kinematic || !pr.world) {
                    VMath.m43Copy(transform, pr.transform);
                    if (pr.world) {
                        pr.world.wakeBody(pr);
                    }
                }
            },
            enumerable: true
        });

        var group = (params.group !== undefined) ? params.group : WebGLPhysicsDevice.prototype.FILTER_STATIC;

        // read only, no getter needed
        Object.defineProperty(rets, "group", {
            value: group,
            enumerable: true
        });

        /*jshint bitwise: false*/
        var mask = (params.mask !== undefined) ? params.mask : (WebGLPhysicsDevice.prototype.FILTER_ALL ^ WebGLPhysicsDevice.prototype.FILTER_STATIC);

        /*jshint bitwise: true*/
        // read only, no getter needed
        Object.defineProperty(rets, "mask", {
            value: mask,
            enumerable: true
        });

        // read/write, side effects needed
        Object.defineProperty(rets, "friction", {
            get: function collisionObjectGetFriction() {
                return this._private.friction;
            },
            set: function collisionObjectSetFriction(friction) {
                var pr = this._private;
                pr.friction = friction;

                // Invalidate arbiter friction values.
                var arbiters = pr.arbiters;
                var i;
                var limit = arbiters.length;
                for (i = 0; i < limit; i += 1) {
                    arbiters[i].invalidateParameters();
                }
            },
            enumerable: true
        });

        // read/write, side effects needed
        Object.defineProperty(rets, "restitution", {
            get: function collisionObjectGetFriction() {
                return this._private.restitution;
            },
            set: function collisionObjectSetFriction(restitution) {
                var pr = this._private;
                pr.restitution = restitution;

                // Invalidate arbiter restitution values.
                var arbiters = pr.arbiters;
                var i;
                var limit = arbiters.length;
                for (i = 0; i < limit; i += 1) {
                    arbiters[i].invalidateParameters();
                }
            },
            enumerable: true
        });

        // read only, no getter needed
        Object.defineProperty(rets, "kinematic", {
            value: kinematic,
            enumerable: true
        });

        //--------------------------------
        // set private collision object properties
        s.group = group;
        s.mask = mask;

        s.kinematic = kinematic;
        s.fixedRotation = !kinematic;

        s.mass = 0;
        s.inverseMass = 0;

        s.inverseInertiaLocal = WebGLPhysicsCollisionObject.sharedInverseInertiaLocal;
        s.inverseInertia = WebGLPhysicsCollisionObject.sharedInverseInertia;

        s.collisionObject = true;

        // Kinematic/Static object is not permitted to sleep in the normal sense.
        s.permitSleep = false;

        // Kinematic/Static objects are not subject to manipulation by continuous
        // collision detection.
        s.sweepFrozen = true;

        // Object default active state is true iff object is kinematic.
        // static object is always 'inactive'
        s.active = kinematic;

        if (params.onPreSolveContact || params.onAddedContacts || params.onProcessedContacts || params.onRemovedContacts) {
            s.contactCallbacks = new WebGLPhysicsContactCallbacks(params, mask);
        } else {
            s.contactCallbacks = null;
        }

        return rets;
    };
    WebGLPhysicsCollisionObject.version = 1;

    WebGLPhysicsCollisionObject.sharedInverseInertiaLocal = VMath.v3BuildZero();
    WebGLPhysicsCollisionObject.sharedInverseInertia = new Float32Array([0, 0, 0, 0, 0, 0, 0, 0, 0]);
    return WebGLPhysicsCollisionObject;
})();

var WebGLPhysicsContactCallbacks = (function () {
    function WebGLPhysicsContactCallbacks(params, mask) {
        this.mask = (params.contactCallbacksMask !== undefined ? params.contactCallbacksMask : mask);
        this.added = false;
        this.deferred = (params.onAddedContacts || params.onProcessedContacts || params.onRemovedContacts);
        this.onPreSolveContact = params.onPreSolveContact || null;
        this.onAddedContacts = params.onAddedContacts || null;
        this.onProcessedContacts = params.onProcessedContacts || null;
        this.onRemovedContacts = params.onRemovedContacts || null;
        this.trigger = params.trigger || false;

        return this;
    }
    return WebGLPhysicsContactCallbacks;
})();

//
// WebGLPhysicsRigidBody
//
// TODO: inherit from WebGLPhysicsCollisionObject
var WebGLPhysicsRigidBody = (function () {
    function WebGLPhysicsRigidBody() {
        this.calculateExtents = WebGLPhysicsCollisionObject.prototype.calculateExtents;
        this.calculateTransform = WebGLPhysicsCollisionObject.prototype.calculateTransform;
    }
    WebGLPhysicsRigidBody.prototype.clone = function () {
        return WebGLPhysicsRigidBody.create(this);
    };

    WebGLPhysicsRigidBody.create = function (params) {
        var retr = new WebGLPhysicsRigidBody();
        var r = new WebGLPhysicsPrivateBody(params, retr);
        retr._private = r;

        // read/write, no side effects
        retr.userData = ("userData" in params) ? params.userData : null;

        // read only, no getter needed
        Object.defineProperty(retr, "shape", {
            value: params.shape,
            enumerable: true
        });

        // read/write, side effects
        Object.defineProperty(retr, "linearVelocity", {
            get: function rigidBodyGetVelocity() {
                var vel = this._private.velocity;
                return VMath.v3Build(vel[0], vel[1], vel[2]);
            },
            set: function rigidBodySetVelocity(linearVelocity) {
                var vel = this._private.velocity;
                vel[0] = linearVelocity[0];
                vel[1] = linearVelocity[1];
                vel[2] = linearVelocity[2];
            },
            enumerable: true
        });

        // read/write, side effects
        Object.defineProperty(retr, "angularVelocity", {
            get: function rigidBodyGetVelocity() {
                var vel = this._private.velocity;
                return VMath.v3Build(vel[3], vel[4], vel[5]);
            },
            set: function rigidBodySetVelocity(angularVelocity) {
                var vel = this._private.velocity;
                vel[3] = angularVelocity[0];
                vel[4] = angularVelocity[1];
                vel[5] = angularVelocity[2];
            },
            enumerable: true
        });

        // read/write, side effects
        Object.defineProperty(retr, "transform", {
            get: function rigidBodyGetTransform() {
                return VMath.m43Copy(this._private.transform);
            },
            set: function rigidBodySetTransform(transform) {
                var pr = this._private;
                VMath.m43Copy(transform, pr.transform);

                // Ensure any arbiter's have their skipDiscreteCollisions flags set to false as
                // new contacts 'will' be needed.
                var arbiters = pr.arbiters;
                var i;
                var limit = arbiters.length;
                for (i = 0; i < limit; i += 1) {
                    arbiters[i].skipDiscreteCollisions = false;
                }
            },
            enumerable: true
        });

        // read/write, side effects
        Object.defineProperty(retr, "active", {
            get: function rigidBodyGetActive() {
                return this._private.active;
            },
            set: function rigidBodySetActive(active) {
                var pr = this._private;
                if (active === pr.active) {
                    if (pr.world && active) {
                        pr.wakeTimeStamp = pr.world.timeStamp;
                    }
                } else if (pr.world) {
                    if (active) {
                        pr.world.wakeBody(pr);
                    } else {
                        var list = pr.world.activeBodies;
                        list[list.indexOf(pr)] = list[list.length - 1];
                        list.pop();
                        pr.active = false;

                        // force any arbiters to be deactivated also.
                        var arbiters = pr.arbiters;
                        var n;
                        var maxN = arbiters.length;
                        for (n = 0; n < maxN; n += 1) {
                            var arb = arbiters[n];
                            if (!arb.active) {
                                continue;
                            }

                            arb.active = false;
                            var worldList = pr.world.activeArbiters;
                            worldList[worldList.indexOf(arb)] = worldList[worldList.length - 1];
                            worldList.pop();
                        }

                        // sync with broadphase
                        pr.world.syncBody(pr);
                    }
                } else {
                    pr.active = active;
                }
            },
            enumerable: true
        });

        // read only, no getter needed
        var group = (params.group !== undefined) ? params.group : WebGLPhysicsDevice.prototype.FILTER_DYNAMIC;
        Object.defineProperty(retr, "group", {
            value: group,
            enumerable: true
        });

        // read only, no getter needed
        var mask = (params.mask !== undefined) ? params.mask : WebGLPhysicsDevice.prototype.FILTER_ALL;
        Object.defineProperty(retr, "mask", {
            value: mask,
            enumerable: true
        });

        // read/write, side effects
        Object.defineProperty(retr, "friction", {
            get: function rigidBodyGetFriction() {
                return this._private.friction;
            },
            set: function rigidBodySetFriction(friction) {
                var pr = this._private;
                pr.friction = friction;

                // Invalidate arbiter friction values.
                var arbiters = pr.arbiters;
                var i;
                var limit = arbiters.length;
                for (i = 0; i < limit; i += 1) {
                    arbiters[i].invalidateParameters();
                }
            },
            enumerable: true
        });
        Object.defineProperty(retr, "restitution", {
            get: function rigidBodyGetRestitution() {
                return this._private.restitution;
            },
            set: function rigidBodySetRestitution(restitution) {
                var pr = this._private;
                pr.restitution = restitution;

                // Invalidate arbiter restitution values.
                var arbiters = pr.arbiters;
                var i;
                var limit = arbiters.length;
                for (i = 0; i < limit; i += 1) {
                    arbiters[i].invalidateParameters();
                }
            },
            enumerable: true
        });

        // read/write, getters needed.
        Object.defineProperty(retr, "linearDamping", {
            get: function rigidBodyGetLinearDamping() {
                return this._private.linearDamping;
            },
            set: function rigidBodySetLinearDamping(linearDamping) {
                this._private.linearDamping = linearDamping;
            },
            enumerable: true
        });
        Object.defineProperty(retr, "angularDamping", {
            get: function rigidBodyGetLinearDamping() {
                return this._private.angularDamping;
            },
            set: function rigidBodySetLinearDamping(angularDamping) {
                this._private.angularDamping = angularDamping;
            },
            enumerable: true
        });

        // read only, no getter needed
        var kinematic = (params.kinematic !== undefined) ? params.kinematic : false;
        Object.defineProperty(retr, "kinematic", {
            value: kinematic,
            enumerable: true
        });

        // read only, no getter needed
        var mass = (params.mass !== undefined) ? params.mass : 1.0;
        Object.defineProperty(retr, "mass", {
            value: mass,
            enumerable: true
        });

        // read only, getter needed for unique vector.
        // this value isn't used internally so is kept in a closure just for this getter.
        var inertia = (params.inertia ? VMath.v3Copy(params.inertia) : VMath.v3ScalarMul(params.shape.inertia, mass));
        Object.defineProperty(retr, "inertia", {
            get: function rigidBodyGetInertia() {
                return VMath.v3Copy(inertia);
            },
            enumerable: true
        });

        // ------------------------------
        // initialise private properties of RigidBody.
        r.group = group;
        r.mask = mask;

        r.active = (params.active !== undefined) ? params.active : (params.frozen !== undefined) ? (!params.frozen) : true;

        r.kinematic = kinematic;
        r.fixedRotation = kinematic || ((params.fixedRotation !== undefined) ? params.fixedRotation : false);

        r.inverseInertiaLocal = (r.fixedRotation ? VMath.v3BuildZero() : VMath.v3Build(1 / inertia[0], 1 / inertia[1], 1 / inertia[2]));
        r.inverseInertia = VMath.m33BuildIdentity();

        r.mass = mass;
        r.inverseMass = (kinematic ? 0 : (1 / r.mass));

        r.collisionObject = false;

        // Kinematic object is not permitted to sleep in the normal sense.
        r.permitSleep = (params.permitSleep !== undefined) ? params.permitSleep : (!kinematic);

        // Kinematic object is not subject to manipulation by continous collisions.
        r.sweepFrozen = kinematic;

        if (params.onPreSolveContact || params.onAddedContacts || params.onProcessedContacts || params.onRemovedContacts) {
            r.contactCallbacks = new WebGLPhysicsContactCallbacks(params, mask);
        } else {
            r.contactCallbacks = null;
        }

        return retr;
    };
    WebGLPhysicsRigidBody.version = 1;
    return WebGLPhysicsRigidBody;
})();

//
// WebGLPhysicsConstraint
//
var WebGLPhysicsConstraint = (function () {
    function WebGLPhysicsConstraint() {
    }
    WebGLPhysicsConstraint.prototype.preStep = function (timeStepRatio, timeStep) {
    };

    WebGLPhysicsConstraint.prototype.applyCachedImpulses = function () {
    };

    WebGLPhysicsConstraint.prototype.computeAndApplyImpulses = function () {
    };

    WebGLPhysicsConstraint.create = function (type, params) {
        var s = new WebGLPhysicsConstraint();

        s.world = null;
        s.userData = null;

        webGLPhysicsClone(s, params);
        s.type = type;

        return s;
    };
    WebGLPhysicsConstraint.version = 1;
    return WebGLPhysicsConstraint;
})();

// Decorate constraint with getter/setters for bodyA/bodyB
// And deal with construction logic common to all constraints
var initConstraintProperties = function initConstraintPropertiesFn(c, params) {
    c.userData = params.userData;

    var pc = c._private;
    pc.world = null;

    // read only, no getter required.
    pc.bodyA = params.bodyA._private;
    Object.defineProperty(c, "bodyA", {
        value: params.bodyA,
        enumerable: true
    });

    // read only, no getter required.
    pc.bodyB = params.bodyB ? params.bodyB._private : null;
    Object.defineProperty(c, "bodyB", {
        value: params.bodyB,
        enumerable: true
    });

    // read/write with side effects
    pc.active = (params.active !== undefined) ? params.active : true;
    Object.defineProperty(c, "active", {
        get: function constraintGetActive() {
            return this._private.active;
        },
        set: function constraintSetActive(active) {
            var pc = this._private;
            if (active === pc.active) {
                if (pc.world && active) {
                    pc.wakeTimeStamp = pc.world.timeStamp;
                }
            } else if (pc.world) {
                if (active) {
                    pc.world.wakeConstraint(pc);
                } else {
                    var list = pc.world.activeConstraints;
                    list[list.indexOf(pc)] = list[list.length - 1];
                    list.pop();
                    pc.active = false;
                }
            } else {
                pc.active = active;
            }
        },
        enumerable: true
    });
};

//
// WebGLPhysicsPoint2Point Constraint
//
var WebGLPhysicsPoint2PointConstraint = (function () {
    function WebGLPhysicsPoint2PointConstraint() {
    }
    WebGLPhysicsPoint2PointConstraint.create = function (params) {
        var c = new WebGLPhysicsPoint2PointConstraint();
        var pc = new WebGLPhysicsPrivatePoint2PointConstraint();
        c._private = pc;

        initConstraintProperties(c, params);

        var data = pc.data;

        // read/write with side effects
        data[0] = params.pivotA[0];
        data[1] = params.pivotA[1];
        data[2] = params.pivotA[2];
        Object.defineProperty(c, "pivotA", {
            get: function point2pointGetPivotA() {
                var data = this._private.data;
                return VMath.v3Build(data[0], data[1], data[2]);
            },
            set: function point2pointSetPivotA(pivotA) {
                var data = this._private.data;
                data[0] = pivotA[0];
                data[1] = pivotA[1];
                data[2] = pivotA[2];
            },
            enumerable: true
        });

        if (params.pivotB) {
            data[3] = params.pivotB[0];
            data[4] = params.pivotB[1];
            data[5] = params.pivotB[2];
        } else {
            var pivotB = VMath.m43TransformPoint(pc.bodyA.transform, params.pivotA);
            data[3] = pivotB[0];
            data[4] = pivotB[1];
            data[5] = pivotB[2];
        }
        Object.defineProperty(c, "pivotB", {
            get: function point2pointGetPivotB() {
                var data = this._private.data;
                return VMath.v3Build(data[3], data[4], data[5]);
            },
            set: function point2pointSetPivotB(pivotB) {
                var data = this._private.data;
                data[3] = pivotB[0];
                data[4] = pivotB[1];
                data[5] = pivotB[2];
            },
            enumerable: true
        });

        // read/write with no immediate side effects, but getter/setter required.
        data[30] = (params.force !== undefined) ? params.force : 0.3;
        Object.defineProperty(c, "force", {
            get: function point2pointGetForce() {
                return this._private.data[30];
            },
            set: function point2pointSetForce(force) {
                this._private.data[30] = force;
            },
            enumerable: true
        });

        // read/write with no immediate side effects, but getter/setter required.
        data[31] = (params.damping !== undefined) ? params.damping : 1.0;
        Object.defineProperty(c, "damping", {
            get: function point2pointGetForce() {
                return this._private.data[31];
            },
            set: function point2pointSetForce(damping) {
                this._private.data[31] = damping;
            },
            enumerable: true
        });

        // read/write with no immediate side effects, but getter/setter required.
        data[32] = (params.impulseClamp !== undefined) ? params.impulseClamp : 0.0;
        Object.defineProperty(c, "impulseClamp", {
            get: function point2pointGetForce() {
                return this._private.data[32];
            },
            set: function point2pointSetForce(impulseClamp) {
                this._private.data[32] = impulseClamp;
            },
            enumerable: true
        });

        return c;
    };
    WebGLPhysicsPoint2PointConstraint.version = 1;
    return WebGLPhysicsPoint2PointConstraint;
})();

WebGLPhysicsPoint2PointConstraint.prototype.type = "POINT2POINT";

var WebGLPhysicsPrivatePoint2PointConstraint = (function () {
    function WebGLPhysicsPrivatePoint2PointConstraint() {
        // Initialise all properties that will ever be set on this object.
        this.bodyA = null;
        this.bodyB = null;

        // [0,  3) : pivotA (vector3)
        // [3,  6) : pivotB (vector3)
        // [6,  9) : relA   (vector3)
        // [9, 12) : relB   (vector3)
        // [12,21) : skewA  (mat33)
        // [21,30) : skewB  (mat33)
        // [30,31) : force   (scalar)
        // [31,32) : damping (scalar)
        // [32,33) : clamp   (scalar)
        // [33,34) : gamma   (scalar)
        // [34,40) : K (symmetric mat33)
        //           [ K0 K1 K2 ]
        //    aka:   [ K1 K3 K4 ]
        //           [ K2 K4 K5 ]
        // [40,43) : jAcc (vector3)
        // [43,46) : bias (vector3)
        this.data = new Float32Array(46);
        return this;
    }
    WebGLPhysicsPrivatePoint2PointConstraint.prototype.preStep = function (timeStepRatio, timeStep) {
        var bodyA = this.bodyA;
        var bodyB = this.bodyB;
        var data = this.data;

        // a0 = this.pivotA
        var a0 = data[0];
        var a1 = data[1];
        var a2 = data[2];

        // b0 = this.pivotB
        var b0 = data[3];
        var b1 = data[4];
        var b2 = data[5];

        // Compute relative coordinates of pivot points.
        //this.relA = VMath.m43TransformVector(this.bodyA.transform, this.pivotA);
        var A = bodyA.transform;
        var ra0 = data[6] = (A[0] * a0) + (A[3] * a1) + (A[6] * a2);
        var ra1 = data[7] = (A[1] * a0) + (A[4] * a1) + (A[7] * a2);
        var ra2 = data[8] = (A[2] * a0) + (A[5] * a1) + (A[8] * a2);

        var rb0, rb1, rb2, B;
        if (bodyB) {
            B = bodyB.transform;

            //this.relB = VMath.m43TransformVector(this.bodyB.transform, this.pivotB);
            rb0 = data[9] = (B[0] * b0) + (B[3] * b1) + (B[6] * b2);
            rb1 = data[10] = (B[1] * b0) + (B[4] * b1) + (B[7] * b2);
            rb2 = data[11] = (B[2] * b0) + (B[5] * b1) + (B[8] * b2);
        }

        //var skew = this.matrix;
        //this.m33BuildSkew(this.relA, skew);
        //VMath.m33Mul(skew, bodyA.inverseInertia, this.skewA);
        var I = bodyA.inverseInertia;
        data[12] = (-ra2 * I[3]) + (ra1 * I[6]);
        data[13] = (-ra2 * I[4]) + (ra1 * I[7]);
        data[14] = (-ra2 * I[5]) + (ra1 * I[8]);
        data[15] = (ra2 * I[0]) + (-ra0 * I[6]);
        data[16] = (ra2 * I[1]) + (-ra0 * I[7]);
        data[17] = (ra2 * I[2]) + (-ra0 * I[8]);
        data[18] = (-ra1 * I[0]) + (ra0 * I[3]);
        data[19] = (-ra1 * I[1]) + (ra0 * I[4]);
        data[20] = (-ra1 * I[2]) + (ra0 * I[5]);

        var mass_sum = bodyA.inverseMass + (bodyB ? bodyB.inverseMass : 0);

        //VMath.m33BuildIdentity(K);
        //VMath.m33ScalarMul(K, mass_sum, K);
        //this.m33Sub(K, VMath.m33Mul(this.skewA, skew), K);
        var K0 = mass_sum + (data[13] * -ra2) + (data[14] * ra1);
        var K3 = mass_sum + (data[15] * ra2) + (data[17] * -ra0);
        var K5 = mass_sum + (data[18] * -ra1) + (data[19] * ra0);
        var K1 = (data[12] * ra2) + (data[14] * -ra0);
        var K2 = (data[12] * -ra1) + (data[13] * ra0);
        var K4 = (data[15] * -ra1) + (data[16] * ra0);

        if (bodyB) {
            //this.m33BuildSkew(this.relB, skew);
            //VMath.m33Mul(skew, bodyB.inverseInertia, this.skewB);
            //this.m33Sub(K, VMath.m33Mul(this.skewB, skew), K);
            I = bodyB.inverseInertia;
            data[21] = (-rb2 * I[3]) + (rb1 * I[6]);
            data[22] = (-rb2 * I[4]) + (rb1 * I[7]);
            data[23] = (-rb2 * I[5]) + (rb1 * I[8]);
            data[24] = (rb2 * I[0]) + (-rb0 * I[6]);
            data[25] = (rb2 * I[1]) + (-rb0 * I[7]);
            data[26] = (rb2 * I[2]) + (-rb0 * I[8]);
            data[27] = (-rb1 * I[0]) + (rb0 * I[3]);
            data[28] = (-rb1 * I[1]) + (rb0 * I[4]);
            data[29] = (-rb1 * I[2]) + (rb0 * I[5]);

            K0 += (data[22] * -rb2) + (data[23] * rb1);
            K3 += (data[24] * rb2) + (data[26] * -rb0);
            K5 += (data[27] * -rb1) + (data[28] * rb0);
            K1 += (data[21] * rb2) + (data[23] * -rb0);
            K2 += (data[21] * -rb1) + (data[22] * rb0);
            K4 += (data[24] * -rb1) + (data[25] * rb0);
        }

        // Soft constraint physics (Based on Nape physics soft constraints).
        //
        // We are given this.force in constraint parameters.
        //   So we must compute suitable omega instead.
        var force = data[30];
        var omega = (2 / timeStep * force * data[31]) / (1 - force);

        var gk = force / (omega * omega);
        var ig = 1 / (1 + gk);
        data[33] = 1 - (gk * ig);

        //VMath.m33Inverse(K, K);
        //VMath.m33ScalarMul(K, ig, K);
        var i0 = ((K3 * K5) - (K4 * K4));
        var i1 = ((K2 * K4) - (K1 * K5));
        var i2 = ((K1 * K4) - (K2 * K3));
        var idet = ig / ((K0 * i0) + (K1 * i1) + (K2 * i2));

        data[34] = (idet * i0);
        data[35] = (idet * i1);
        data[36] = (idet * i2);
        data[37] = (idet * ((K0 * K5) - (K2 * K2)));
        data[38] = (idet * ((K1 * K2) - (K0 * K4)));
        data[39] = (idet * ((K0 * K3) - (K1 * K1)));

        // positional error
        var C0 = ra0 + A[9];
        var C1 = ra1 + A[10];
        var C2 = ra2 + A[11];
        if (bodyB) {
            C0 -= rb0 + B[9];
            C1 -= rb1 + B[10];
            C2 -= rb2 + B[11];
        } else {
            C0 -= b0;
            C1 -= b1;
            C2 -= b2;
        }

        // soft constraint bias.
        var scale = -force / timeStep;
        data[43] = (C0 * scale);
        data[44] = (C1 * scale);
        data[45] = (C2 * scale);

        // scale cached impulse for change in time step.
        data[40] *= timeStepRatio;
        data[41] *= timeStepRatio;
        data[42] *= timeStepRatio;
    };

    WebGLPhysicsPrivatePoint2PointConstraint.prototype.applyCachedImpulses = function () {
        var data = this.data;

        // var j = this.jAcc
        var j0 = data[40];
        var j1 = data[41];
        var j2 = data[42];

        var bodyA = this.bodyA;
        var vel = bodyA.velocity;
        var imass = bodyA.inverseMass;
        vel[0] += (j0 * imass);
        vel[1] += (j1 * imass);
        vel[2] += (j2 * imass);

        //var I = this.skewA;
        vel[3] -= ((data[12] * j0) + (data[15] * j1) + (data[18] * j2));
        vel[4] -= ((data[13] * j0) + (data[16] * j1) + (data[19] * j2));
        vel[5] -= ((data[14] * j0) + (data[17] * j1) + (data[20] * j2));

        var bodyB = this.bodyB;
        if (bodyB) {
            vel = bodyB.velocity;
            imass = bodyB.inverseMass;
            vel[0] -= (j0 * imass);
            vel[1] -= (j1 * imass);
            vel[2] -= (j2 * imass);

            //I = this.skewB;
            vel[3] += ((data[21] * j0) + (data[24] * j1) + (data[27] * j2));
            vel[4] += ((data[22] * j0) + (data[25] * j1) + (data[28] * j2));
            vel[5] += ((data[23] * j0) + (data[26] * j1) + (data[29] * j2));
        }
    };

    WebGLPhysicsPrivatePoint2PointConstraint.prototype.computeAndApplyImpulses = function () {
        var bodyA = this.bodyA;
        var bodyB = this.bodyB;
        var data = this.data;

        // jAcc = this.jAcc
        var jAcc0 = data[40];
        var jAcc1 = data[41];
        var jAcc2 = data[42];

        // velocity bias, minus the relative velocity at pivot points
        // stored in (l0, l1, l2)
        var vel1 = bodyA.velocity;

        //var rel = this.relA;
        // l = bias - (vel1 + ang1 cross rel)
        var l0 = data[43] - (vel1[0] + (vel1[4] * data[8]) - (vel1[5] * data[7]));
        var l1 = data[44] - (vel1[1] + (vel1[5] * data[6]) - (vel1[3] * data[8]));
        var l2 = data[45] - (vel1[2] + (vel1[3] * data[7]) - (vel1[4] * data[6]));

        var vel2;
        if (bodyB) {
            vel2 = bodyB.velocity;

            //rel = this.relB;
            // l += vel2 + ang2 cross rel
            l0 += (vel2[0] + (vel2[4] * data[11]) - (vel2[5] * data[10]));
            l1 += (vel2[1] + (vel2[5] * data[9]) - (vel2[3] * data[11]));
            l2 += (vel2[2] + (vel2[3] * data[10]) - (vel2[4] * data[9]));
        }

        // compute, and accumulate impulse into (jAcc0, jAcc1, jAcc2)
        var gamma = data[33];

        //var K = this.K;
        // jAcc = jAcc * gamma + K * l
        jAcc0 = (jAcc0 * gamma) + (data[34] * l0) + (data[35] * l1) + (data[36] * l2);
        jAcc1 = (jAcc1 * gamma) + (data[35] * l0) + (data[37] * l1) + (data[38] * l2);
        jAcc2 = (jAcc2 * gamma) + (data[36] * l0) + (data[38] * l1) + (data[39] * l2);

        var clamp = data[32];
        if (clamp !== 0) {
            // clamp accumulated impulse.
            var jlsq = (jAcc0 * jAcc0) + (jAcc1 * jAcc1) + (jAcc2 * jAcc2);
            if (jlsq > clamp * clamp) {
                jlsq = clamp / Math.sqrt(jlsq);
                jAcc0 *= jlsq;
                jAcc1 *= jlsq;
                jAcc2 *= jlsq;
            }
        }

        // compute impulse to apply, and store new cached impulse.
        var j0 = jAcc0 - data[40];
        var j1 = jAcc1 - data[41];
        var j2 = jAcc2 - data[42];
        data[40] = jAcc0;
        data[41] = jAcc1;
        data[42] = jAcc2;

        // Apply impulse.
        var imass = bodyA.inverseMass;
        vel1[0] += (j0 * imass);
        vel1[1] += (j1 * imass);
        vel1[2] += (j2 * imass);

        //var I = this.skewA;
        vel1[3] -= ((data[12] * j0) + (data[15] * j1) + (data[18] * j2));
        vel1[4] -= ((data[13] * j0) + (data[16] * j1) + (data[19] * j2));
        vel1[5] -= ((data[14] * j0) + (data[17] * j1) + (data[20] * j2));

        if (bodyB) {
            imass = bodyB.inverseMass;
            vel2[0] -= (j0 * imass);
            vel2[1] -= (j1 * imass);
            vel2[2] -= (j2 * imass);

            //I = this.skewB;
            vel2[3] += ((data[21] * j0) + (data[24] * j1) + (data[27] * j2));
            vel2[4] += ((data[22] * j0) + (data[25] * j1) + (data[28] * j2));
            vel2[5] += ((data[23] * j0) + (data[26] * j1) + (data[29] * j2));
        }
    };
    return WebGLPhysicsPrivatePoint2PointConstraint;
})();

//
// WebGLPhysicsCharacter
//
var WebGLPhysicsCharacter = (function () {
    function WebGLPhysicsCharacter() {
    }
    WebGLPhysicsCharacter.prototype.jump = function () {
        var pc = this._private;
        var rigidBody = pc.rigidBody._private;
        var world = rigidBody.world;
        if (world) {
            rigidBody.velocity[1] = Math.sqrt(-2 * (this.maxJumpHeight - this.stepHeight) * world.gravity[1]);
            rigidBody.transform[10] += this.stepHeight;
            world.wakeBody(rigidBody);
        }
    };

    WebGLPhysicsCharacter.prototype.calculateExtents = function (extents) {
        this._private.rigidBody.calculateExtents(extents);
    };

    WebGLPhysicsCharacter.prototype.calculateTransform = function (transform, origin) {
        this._private.rigidBody.calculateTransform(transform, origin);
    };

    WebGLPhysicsCharacter.create = function (params) {
        var c = new WebGLPhysicsCharacter();
        var pc = new WebGLPhysicsPrivateCharacter();
        c._private = pc;

        // read/write, no side effects.
        c.userData = (params.userData !== undefined) ? params.userData : null;

        // read/write with side effects.
        Object.defineProperty(c, "crouch", {
            get: function getCharacterCrouchFn() {
                return this._private.crouch;
            },
            set: function setCharacterCrouchFn(crouch) {
                var pc = this._private;
                if (!pc.dead && crouch !== pc.crouch) {
                    var rigidBody = pc.rigidBody._private;
                    var capsule = rigidBody.shape;

                    pc.crouch = crouch;
                    if (crouch) {
                        capsule.halfHeight = ((this.crouchHeight * 0.5) - this.radius);
                        rigidBody.transform[10] -= ((this.height - this.crouchHeight) * 0.5);
                    } else {
                        capsule.halfHeight = ((this.height * 0.5) - this.radius);
                        rigidBody.transform[10] += ((this.height - this.crouchHeight) * 0.5);
                    }

                    if (rigidBody.world) {
                        rigidBody.world.wakeBody(rigidBody);
                    }
                }
            },
            enumerable: true
        });

        // read/write with side effects.
        Object.defineProperty(c, "dead", {
            get: function getCharacterDeadFn() {
                return this._private.dead;
            },
            set: function setCharacterDead(dead) {
                var pc = this._private;
                if (pc.dead !== dead) {
                    var rigidBody = pc.rigidBody._private;
                    var capsule = rigidBody.shape;

                    pc.dead = dead;
                    if (dead) {
                        capsule.halfHeight = 0;
                        rigidBody.transform[10] -= ((this.height - this.radius) * 0.5);
                    } else {
                        capsule.halfHeight = ((this.height * 0.5) - this.radius);
                        rigidBody.transform[10] += ((this.height - this.radius) * 0.5);
                    }

                    if (rigidBody.world) {
                        rigidBody.world.wakeBody(rigidBody);
                    }
                }
            },
            enumerable: true
        });

        // read only, no getter required.
        Object.defineProperty(c, "height", {
            value: params.height,
            enumerable: true
        });

        // read only, no getter required.
        Object.defineProperty(c, "radius", {
            value: params.radius,
            enumerable: true
        });

        // read only, no getter required
        Object.defineProperty(c, "stepHeight", {
            value: (params.stepHeight !== undefined) ? params.stepHeight : 0.35,
            enumerable: true
        });

        // read/write, no side effects so stored on actual object as standard property.
        c.maxJumpHeight = (params.maxJumpHeight !== undefined) ? params.maxJumpHeight : 1;

        // read only, no getter required
        Object.defineProperty(c, "crouchHeight", {
            value: (params.crouchHeight !== undefined) ? params.crouchHeight : (0.5 * params.height),
            enumerable: true
        });

        // read only, getter required.
        Object.defineProperty(c, "onGround", {
            get: function getCharacterOnGround() {
                var pc = this._private;
                var rigidBody = pc.rigidBody._private;

                if (rigidBody.world) {
                    var pos = rigidBody.transform;
                    var start = pc.start;
                    var end = pc.end;

                    start[9] = pos[9];
                    start[10] = pos[10];
                    start[11] = pos[11];

                    end[9] = pos[9];
                    end[10] = (pos[10] - (this.stepHeight * 0.5));
                    end[11] = pos[11];

                    var result = rigidBody.world.convexSweepTest({
                        shape: rigidBody.shape._public,
                        from: start,
                        to: end,
                        group: WebGLPhysicsDevice.prototype.FILTER_CHARACTER
                    }, pc.onGroundConvexCallback);
                    return (result !== null);
                } else {
                    return false;
                }
            },
            enumerable: true
        });

        // read/write with side effects.
        Object.defineProperty(c, "position", {
            get: function getCharacterPosition() {
                var rigidBody = this._private.rigidBody;
                return VMath.m43Pos(rigidBody._private.transform);
            },
            set: function setCharacterPosition(position) {
                var rigidBody = this._private.rigidBody;
                var transform = rigidBody._private.transform;
                transform[9] = position[0];
                transform[10] = position[1];
                transform[11] = position[2];
                rigidBody.transform = rigidBody._private.transform;
                rigidBody.active = true;
            },
            enumerable: true
        });

        // read/write with side effects.
        Object.defineProperty(c, "velocity", {
            get: function getCharacterVelocity() {
                var rigidBody = this._private.rigidBody;
                return rigidBody.linearVelocity;
            },
            set: function setCharacterVelocity(velocity) {
                var rigidBody = this._private.rigidBody;
                rigidBody.linearVelocity = velocity;
                rigidBody.active = true;
            },
            enumerable: true
        });

        // read only, no getter required.
        var group = (params.group !== undefined) ? params.group : WebGLPhysicsDevice.prototype.FILTER_CHARACTER;
        Object.defineProperty(c, "group", {
            value: group,
            enumerable: true
        });

        // read only, no getter required.
        var mask = (params.mask !== undefined) ? params.mask : WebGLPhysicsDevice.prototype.FILTER_ALL;
        Object.defineProperty(c, "mask", {
            value: mask,
            enumerable: true
        });

        // Create inner RigidBody with Capsule shape.
        var capsule = WebGLPhysicsCapsuleShape.create({
            radius: c.radius,
            height: (2 * ((c.height * 0.5) - c.radius)),
            margin: 0
        });

        var rigidBody = WebGLPhysicsRigidBody.create({
            shape: capsule,
            mass: params.mass,
            transform: params.transform,
            linearVelocity: params.velocity,
            group: group,
            mask: mask,
            friction: params.friction,
            restitution: params.restitution,
            linearDamping: params.linearDamping,
            angularDamping: params.angularDamping,
            fixedRotation: true
        });

        // private (But internals like dynamics world need access through this object).
        pc.rigidBody = rigidBody;

        // Back reference to this public character, so that rayTests and
        // convexSweeps can in the case of intersecting a rigid body that
        // represents a character, return the character instead!
        // TODO: Here a WebGLPhysicsCharacter is assign to a property that
        // should be a WebGLPhysicsCollisionObject.  Can PhysicsCharacter
        // inherit from CollisionObject?
        rigidBody._private._public = c;

        return c;
    };
    WebGLPhysicsCharacter.version = 1;
    return WebGLPhysicsCharacter;
})();

var WebGLPhysicsPrivateCharacter = (function () {
    function WebGLPhysicsPrivateCharacter() {
        // Initialise all properties this object will ever hold.
        // Value of read/write property.
        this.crouch = false;
        this.dead = false;

        // Matrices re-used in all calls to onGround getter.
        this.start = VMath.m43BuildIdentity();
        this.end = VMath.m43BuildIdentity();

        // Reference to created RigidBody representing Character.
        this.rigidBody = null;

        return this;
    }
    WebGLPhysicsPrivateCharacter.prototype.onGroundConvexCallback = function (hitResult) {
        // Less than cosine of 15 degrees.
        return hitResult.hitNormal[1] >= 0.26;
    };
    WebGLPhysicsPrivateCharacter.version = 1;
    return WebGLPhysicsPrivateCharacter;
})();

//
// WebGL GJK Contact Solver
//
var WebGLGJKContactSolver = (function () {
    function WebGLGJKContactSolver() {
    }
    WebGLGJKContactSolver.prototype.removeVertex = function (index) {
        this.numVertices -= 1;

        var simplex = this.simplex;
        var replace = (index * 9);
        var withv = (this.numVertices * 9);

        simplex[replace] = simplex[withv];
        simplex[replace + 1] = simplex[withv + 1];
        simplex[replace + 2] = simplex[withv + 2];
        simplex[replace + 3] = simplex[withv + 3];
        simplex[replace + 4] = simplex[withv + 4];
        simplex[replace + 5] = simplex[withv + 5];
        simplex[replace + 6] = simplex[withv + 6];
        simplex[replace + 7] = simplex[withv + 7];
        simplex[replace + 8] = simplex[withv + 8];
    };

    WebGLGJKContactSolver.prototype.reduceVertices = function (coords) {
        if (this.numVertices >= 4 && coords[3] === 0) {
            this.numVertices -= 1;
        }

        var simplex = this.simplex;
        var withv;
        if (this.numVertices >= 3 && coords[2] === 0) {
            this.numVertices -= 1;
            withv = (this.numVertices * 9);
            simplex[18] = simplex[withv];
            simplex[19] = simplex[withv + 1];
            simplex[20] = simplex[withv + 2];
            simplex[21] = simplex[withv + 3];
            simplex[22] = simplex[withv + 4];
            simplex[23] = simplex[withv + 5];
            simplex[24] = simplex[withv + 6];
            simplex[25] = simplex[withv + 7];
            simplex[26] = simplex[withv + 8];
        }

        if (this.numVertices >= 2 && coords[1] === 0) {
            this.numVertices -= 1;
            withv = (this.numVertices * 9);
            simplex[9] = simplex[withv];
            simplex[10] = simplex[withv + 1];
            simplex[11] = simplex[withv + 2];
            simplex[12] = simplex[withv + 3];
            simplex[13] = simplex[withv + 4];
            simplex[14] = simplex[withv + 5];
            simplex[15] = simplex[withv + 6];
            simplex[16] = simplex[withv + 7];
            simplex[17] = simplex[withv + 8];
        }

        if (this.numVertices >= 1 && coords[0] === 0) {
            this.numVertices -= 1;
            withv = (this.numVertices * 9);
            simplex[0] = simplex[withv];
            simplex[1] = simplex[withv + 1];
            simplex[2] = simplex[withv + 2];
            simplex[3] = simplex[withv + 3];
            simplex[4] = simplex[withv + 4];
            simplex[5] = simplex[withv + 5];
            simplex[6] = simplex[withv + 6];
            simplex[7] = simplex[withv + 7];
            simplex[8] = simplex[withv + 8];
        }
    };

    WebGLGJKContactSolver.prototype.updateClosestPoints = function () {
        var numVertices = this.numVertices;
        if (numVertices === 0) {
            return false;
        }

        // ----------------------------------------
        // single vertex, only one candidate point.
        var simplex = this.simplex;
        var closest = this.closest;
        var i;

        if (numVertices === 1) {
            closest[0] = simplex[3];
            closest[1] = simplex[4];
            closest[2] = simplex[5];
            closest[3] = simplex[6];
            closest[4] = simplex[7];
            closest[5] = simplex[8];
            return true;
        }

        // ----------------------------------------
        // two vertices, find closest point on line
        var a0 = simplex[0];
        var a1 = simplex[1];
        var a2 = simplex[2];

        var b0 = simplex[9];
        var b1 = simplex[10];
        var b2 = simplex[11];

        if (numVertices === 2) {
            var w0 = (a0 - b0);
            var w1 = (a1 - b1);
            var w2 = (a2 - b2);

            var dot = ((a0 * w0) + (a1 * w1) + (a2 * w2));
            if (dot > 0) {
                var wlsq = ((w0 * w0) + (w1 * w1) + (w2 * w2));
                if (dot < wlsq) {
                    dot /= wlsq;
                    var dot1 = (1.0 - dot);

                    closest[0] = ((simplex[3] * dot1) + (simplex[12] * dot));
                    closest[1] = ((simplex[4] * dot1) + (simplex[13] * dot));
                    closest[2] = ((simplex[5] * dot1) + (simplex[14] * dot));
                    closest[3] = ((simplex[6] * dot1) + (simplex[15] * dot));
                    closest[4] = ((simplex[7] * dot1) + (simplex[16] * dot));
                    closest[5] = ((simplex[8] * dot1) + (simplex[17] * dot));

                    return true;
                } else {
                    this.removeVertex(0);
                }
            } else {
                this.removeVertex(1);
            }

            for (i = 0; i < 6; i += 1) {
                closest[i] = simplex[i + 3];
            }

            return true;
        }

        // ----------------------------------------
        // 3 vertices, find closest point in triangle
        var coords = this.cachedCoords;
        var alpha, beta, gamma;

        if (numVertices === 3) {
            this.closestPointTriangle(0, 9, 18, coords);
            this.reduceVertices(coords);

            alpha = coords[0];
            beta = coords[1];
            gamma = coords[2];

            closest[0] = ((alpha * simplex[3]) + (beta * simplex[12]) + (gamma * simplex[21]));
            closest[1] = ((alpha * simplex[4]) + (beta * simplex[13]) + (gamma * simplex[22]));
            closest[2] = ((alpha * simplex[5]) + (beta * simplex[14]) + (gamma * simplex[23]));
            closest[3] = ((alpha * simplex[6]) + (beta * simplex[15]) + (gamma * simplex[24]));
            closest[4] = ((alpha * simplex[7]) + (beta * simplex[16]) + (gamma * simplex[25]));
            closest[5] = ((alpha * simplex[8]) + (beta * simplex[17]) + (gamma * simplex[26]));

            return true;
        }

        if (numVertices === 4) {
            var outside = this.closestPointTetrahedron(coords);
            if (outside) {
                this.reduceVertices(coords);

                alpha = coords[0];
                beta = coords[1];
                gamma = coords[2];
                var delta = coords[3];

                closest[0] = ((alpha * simplex[3]) + (beta * simplex[12]) + (gamma * simplex[21]) + (delta * simplex[30]));
                closest[1] = ((alpha * simplex[4]) + (beta * simplex[13]) + (gamma * simplex[22]) + (delta * simplex[31]));
                closest[2] = ((alpha * simplex[5]) + (beta * simplex[14]) + (gamma * simplex[23]) + (delta * simplex[32]));
                closest[3] = ((alpha * simplex[6]) + (beta * simplex[15]) + (gamma * simplex[24]) + (delta * simplex[33]));
                closest[4] = ((alpha * simplex[7]) + (beta * simplex[16]) + (gamma * simplex[25]) + (delta * simplex[34]));
                closest[5] = ((alpha * simplex[8]) + (beta * simplex[17]) + (gamma * simplex[26]) + (delta * simplex[35]));

                return true;
            } else {
                return false;
            }
        }

        return false;
    };

    WebGLGJKContactSolver.prototype.closestPointTetrahedron = function (coords) {
        var simplex = this.simplex;

        var a0 = simplex[0];
        var a1 = simplex[1];
        var a2 = simplex[2];

        var b0 = simplex[9];
        var b1 = simplex[10];
        var b2 = simplex[11];

        var c0 = simplex[18];
        var c1 = simplex[19];
        var c2 = simplex[20];

        var d0 = simplex[27];
        var d1 = simplex[28];
        var d2 = simplex[29];

        var ab0 = (b0 - a0);
        var ab1 = (b1 - a1);
        var ab2 = (b2 - a2);

        var ac0 = (c0 - a0);
        var ac1 = (c1 - a1);
        var ac2 = (c2 - a2);

        var ad0 = (d0 - a0);
        var ad1 = (d1 - a1);
        var ad2 = (d2 - a2);

        var bc0 = (c0 - b0);
        var bc1 = (c1 - b1);
        var bc2 = (c2 - b2);

        var bd0 = (d0 - b0);
        var bd1 = (d1 - b1);
        var bd2 = (d2 - b2);

        var n0, n1, n2, signD, signOrigin;

        n0 = ((ab1 * ac2) - (ab2 * ac1));
        n1 = ((ab2 * ac0) - (ab0 * ac2));
        n2 = ((ab0 * ac1) - (ab1 * ac0));
        signD = ((ad0 * n0) + (ad1 * n1) + (ad2 * n2));
        signOrigin = -((a0 * n0) + (a1 * n1) + (a2 * n2));
        var sideABC = ((signOrigin * signD) <= 0);

        n0 = ((ac1 * ad2) - (ac2 * ad1));
        n1 = ((ac2 * ad0) - (ac0 * ad2));
        n2 = ((ac0 * ad1) - (ac1 * ad0));
        signD = ((ab0 * n0) + (ab1 * n1) + (ab2 * n2));
        signOrigin = -((a0 * n0) + (a1 * n1) + (a2 * n2));
        var sideACD = ((signOrigin * signD) <= 0);

        n0 = ((ad1 * ab2) - (ad2 * ab1));
        n1 = ((ad2 * ab0) - (ad0 * ab2));
        n2 = ((ad0 * ab1) - (ad1 * ab0));
        signD = ((ac0 * n0) + (ac1 * n1) + (ac2 * n2));
        signOrigin = -((a0 * n0) + (a1 * n1) + (a2 * n2));
        var sideADB = ((signOrigin * signD) <= 0);

        n0 = ((bd1 * bc2) - (bd2 * bc1));
        n1 = ((bd2 * bc0) - (bd0 * bc2));
        n2 = ((bd0 * bc1) - (bd1 * bc0));
        signD = ((ab0 * n0) + (ab1 * n1) + (ab2 * n2));
        signOrigin = ((b0 * n0) + (b1 * n1) + (b2 * n2));
        var sideBDC = ((signOrigin * signD) <= 0);

        coords[0] = coords[1] = coords[2] = coords[3] = 0.0;

        if (!sideABC && !sideACD && !sideADB && !sideBDC) {
            return false;
        }

        var tempCoords = this.tempCoords;
        var minSqDist = Number.MAX_VALUE;
        var sqDist;

        if (sideABC) {
            sqDist = this.closestPointTriangle(0, 9, 18, tempCoords, true);
            if (sqDist < minSqDist) {
                minSqDist = sqDist;
                coords[0] = tempCoords[0];
                coords[1] = tempCoords[1];
                coords[2] = tempCoords[2];
                coords[3] = 0.0;
            }
        }

        if (sideACD) {
            sqDist = this.closestPointTriangle(0, 18, 27, tempCoords, true);
            if (sqDist < minSqDist) {
                minSqDist = sqDist;
                coords[0] = tempCoords[0];
                coords[1] = 0.0;
                coords[2] = tempCoords[1];
                coords[3] = tempCoords[2];
            }
        }

        if (sideADB) {
            sqDist = this.closestPointTriangle(0, 27, 9, tempCoords, true);
            if (sqDist < minSqDist) {
                minSqDist = sqDist;
                coords[0] = tempCoords[0];
                coords[1] = tempCoords[2];
                coords[2] = 0.0;
                coords[3] = tempCoords[1];
            }
        }

        if (sideBDC) {
            sqDist = this.closestPointTriangle(9, 27, 18, tempCoords, true);
            if (sqDist < minSqDist) {
                minSqDist = sqDist;
                coords[0] = 0.0;
                coords[1] = tempCoords[0];
                coords[2] = tempCoords[2];
                coords[3] = tempCoords[1];
            }
        }

        return true;
    };

    WebGLGJKContactSolver.prototype.closestPointTriangle = function (a, b, c, coords, computeDistance) {
        var simplex = this.simplex;

        var a0 = simplex[a];
        var a1 = simplex[a + 1];
        var a2 = simplex[a + 2];

        var b0 = simplex[b];
        var b1 = simplex[b + 1];
        var b2 = simplex[b + 2];

        var c0 = simplex[c];
        var c1 = simplex[c + 1];
        var c2 = simplex[c + 2];

        var ba0 = (a0 - b0);
        var ba1 = (a1 - b1);
        var ba2 = (a2 - b2);

        var ca0 = (a0 - c0);
        var ca1 = (a1 - c1);
        var ca2 = (a2 - c2);

        var dot1 = ((a0 * ba0) + (a1 * ba1) + (a2 * ba2));
        var dot2 = ((a0 * ca0) + (a1 * ca1) + (a2 * ca2));
        if (dot1 <= 0.0 && dot2 <= 0) {
            coords[0] = 1;
            coords[1] = coords[2] = 0;
            if (computeDistance) {
                return ((a0 * a0) + (a1 * a1) + (a2 * a2));
            } else {
                return undefined;
            }
        }

        var dot3 = ((b0 * ba0) + (b1 * ba1) + (b2 * ba2));
        var dot4 = ((b0 * ca0) + (b1 * ca1) + (b2 * ca2));
        if (dot3 >= 0.0 && dot4 <= dot3) {
            coords[1] = 1;
            coords[0] = coords[2] = 0;
            if (computeDistance) {
                return ((b0 * b0) + (b1 * b1) + (b2 * b2));
            } else {
                return undefined;
            }
        }

        var v;
        var d0, d1, d2;

        var vc = ((dot1 * dot4) - (dot3 * dot2));
        if (vc <= 0.0 && dot1 >= 0.0 && dot3 <= 0.0) {
            v = (dot1 / (dot1 - dot3));
            coords[0] = (1 - v);
            coords[1] = v;
            coords[2] = 0;
            if (computeDistance) {
                d0 = (a0 - (v * ba0));
                d1 = (a1 - (v * ba1));
                d2 = (a2 - (v * ba2));
                return ((d0 * d0) + (d1 * d1) + (d2 * d2));
            } else {
                return undefined;
            }
        }

        var dot5 = ((c0 * ba0) + (c1 * ba1) + (c2 * ba2));
        var dot6 = ((c0 * ca0) + (c1 * ca1) + (c2 * ca2));
        if (dot6 >= 0.0 && dot5 <= dot6) {
            coords[0] = coords[1] = 0;
            coords[2] = 1;
            if (computeDistance) {
                return ((c0 * c0) + (c1 * c1) + (c2 * c2));
            } else {
                return undefined;
            }
        }

        var vb = ((dot5 * dot2) - (dot1 * dot6));
        if (vb <= 0.0 && dot2 >= 0.0 && dot6 <= 0.0) {
            v = (dot2 / (dot2 - dot6));
            coords[0] = (1 - v);
            coords[1] = 0;
            coords[2] = v;
            if (computeDistance) {
                d0 = (a0 - (v * ca0));
                d1 = (a1 - (v * ca1));
                d2 = (a2 - (v * ca2));
                return ((d0 * d0) + (d1 * d1) + (d2 * d2));
            } else {
                return undefined;
            }
        }

        var va = ((dot3 * dot6) - (dot5 * dot4));
        if (va <= 0.0 && (dot4 - dot3) >= 0.0 && (dot5 - dot6) >= 0.0) {
            v = ((dot4 - dot3) / ((dot4 - dot3) + (dot5 - dot6)));
            coords[0] = 0;
            coords[1] = (1 - v);
            coords[2] = v;
            if (computeDistance) {
                d0 = ((b0 * (1 - v)) + (c0 * v));
                d1 = ((b1 * (1 - v)) + (c1 * v));
                d2 = ((b2 * (1 - v)) + (c2 * v));
                return ((d0 * d0) + (d1 * d1) + (d2 * d2));
            } else {
                return undefined;
            }
        }

        // Origin contained in triangle region
        var denom = (1 / (va + vb + vc));
        v = (vb * denom);
        var w = (vc * denom);
        coords[0] = (1 - v - w);
        coords[1] = v;
        coords[2] = w;
        if (computeDistance) {
            d0 = (a0 - (ba0 * v) - (ca0 * w));
            d1 = (a1 - (ba1 * v) - (ca1 * w));
            d2 = (a2 - (ba2 * v) - (ca2 * w));
            return ((d0 * d0) + (d1 * d1) + (d2 * d2));
        } else {
            return undefined;
        }
    };

    //
    // cache having properties
    //   shapeA
    //   shapeB
    //   axis <-- to be mutated by this function
    //      'on' objectB.
    //   closestA <-- to be populated by this function
    //   closestB <-- to be populated by this function
    WebGLGJKContactSolver.prototype.evaluate = function (cache, xformA, xformB) {
        var axis = cache.axis;
        var shapeA = cache.shapeA;
        var shapeB = cache.shapeB;

        // Reset GJK.
        this.numVertices = 0;
        var lastW0, lastW1, lastW2;
        lastW0 = lastW1 = lastW2 = Number.MAX_VALUE;

        var curIter = 0;
        var maxIter = 100;
        var seperated = false;

        var squaredDistance = Number.MAX_VALUE;

        // Cached for frequent access.
        var A0 = xformA[0];
        var A1 = xformA[1];
        var A2 = xformA[2];
        var A3 = xformA[3];
        var A4 = xformA[4];
        var A5 = xformA[5];
        var A6 = xformA[6];
        var A7 = xformA[7];
        var A8 = xformA[8];
        var A9 = xformA[9];
        var A10 = xformA[10];
        var A11 = xformA[11];

        var B0 = xformB[0];
        var B1 = xformB[1];
        var B2 = xformB[2];
        var B3 = xformB[3];
        var B4 = xformB[4];
        var B5 = xformB[5];
        var B6 = xformB[6];
        var B7 = xformB[7];
        var B8 = xformB[8];
        var B9 = xformB[9];
        var B10 = xformB[10];
        var B11 = xformB[11];

        var axis0 = axis[0];
        var axis1 = axis[1];
        var axis2 = axis[2];
        var axislsq;

        var supportA = cache.closestA;
        var supportB = cache.closestB;

        var closest = this.closest;
        var simplex = this.simplex;

        // Epsilon defined based on rough experimental result.
        var equalVertexThreshold = 1e-4;

        for (; ;) {
            curIter += 1;

            // supportA = xformA * shapeA.localSupport ( - ixformA * axis)
            // supportB = xformB * shapeB.localSupport (   ixformB * axis)
            //this.m43InverseOrthonormalTransformVector(xformA, axis, supportA);
            //VMath.v3Neg(supportA, supportA);
            supportA[0] = -((A0 * axis0) + (A1 * axis1) + (A2 * axis2));
            supportA[1] = -((A3 * axis0) + (A4 * axis1) + (A5 * axis2));
            supportA[2] = -((A6 * axis0) + (A7 * axis1) + (A8 * axis2));

            //this.m43InverseOrthonormalTransformVector(xformB, axis, supportB);
            supportB[0] = ((B0 * axis0) + (B1 * axis1) + (B2 * axis2));
            supportB[1] = ((B3 * axis0) + (B4 * axis1) + (B5 * axis2));
            supportB[2] = ((B6 * axis0) + (B7 * axis1) + (B8 * axis2));

            shapeA.localSupportWithoutMargin(supportA, supportA);
            shapeB.localSupportWithoutMargin(supportB, supportB);

            //VMath.m43TransformPoint(xformA, supportA, supportA);
            var d0 = supportA[0];
            var d1 = supportA[1];
            var d2 = supportA[2];
            var sa0 = supportA[0] = ((A0 * d0) + (A3 * d1) + (A6 * d2) + A9);
            var sa1 = supportA[1] = ((A1 * d0) + (A4 * d1) + (A7 * d2) + A10);
            var sa2 = supportA[2] = ((A2 * d0) + (A5 * d1) + (A8 * d2) + A11);

            //VMath.m43TransformPoint(xformB, supportB, supportB);
            d0 = supportB[0];
            d1 = supportB[1];
            d2 = supportB[2];
            var sb0 = supportB[0] = ((B0 * d0) + (B3 * d1) + (B6 * d2) + B9);
            var sb1 = supportB[1] = ((B1 * d0) + (B4 * d1) + (B7 * d2) + B10);
            var sb2 = supportB[2] = ((B2 * d0) + (B5 * d1) + (B8 * d2) + B11);

            //VMath.v3Sub(supportA, supportB, w);
            var w0 = sa0 - sb0;
            var w1 = sa1 - sb1;
            var w2 = sa2 - sb2;

            // If point is already in simplex, then we have reached closest point to origin
            // and minkowski difference does not intersect origin.
            var inSimplex = false;
            var index = this.numVertices * 9;
            var i;
            for (i = 0; i < index; i += 9) {
                d0 = (w0 - simplex[i]);
                d1 = (w1 - simplex[i + 1]);
                d2 = (w2 - simplex[i + 2]);
                if (((d0 * d0) + (d1 * d1) + (d2 * d2)) < equalVertexThreshold) {
                    inSimplex = true;
                }
            }

            if (!inSimplex) {
                d0 = (w0 - lastW0);
                d1 = (w1 - lastW1);
                d2 = (w2 - lastW2);
                inSimplex = ((d0 * d0) + (d1 * d1) + (d2 * d2)) < equalVertexThreshold;
            }

            if (inSimplex) {
                seperated = true;
                break;
            }

            //delta = VMath.v3Dot(axis, w);
            var delta = (axis0 * w0) + (axis1 * w1) + (axis2 * w2);

            if ((squaredDistance - delta) <= (squaredDistance * WebGLPhysicsConfig.GJK_FRACTIONAL_THRESHOLD)) {
                seperated = true;
                break;
            }

            // Add vertex to simplex.
            lastW0 = simplex[index] = w0;
            lastW1 = simplex[index + 1] = w1;
            lastW2 = simplex[index + 2] = w2;
            simplex[index + 3] = sa0;
            simplex[index + 4] = sa1;
            simplex[index + 5] = sa2;
            simplex[index + 6] = sb0;
            simplex[index + 7] = sb1;
            simplex[index + 8] = sb2;
            this.numVertices += 1;

            if (!this.updateClosestPoints()) {
                seperated = false;
                break;
            }

            d0 = (closest[0] - closest[3]);
            d1 = (closest[1] - closest[4]);
            d2 = (closest[2] - closest[5]);

            // If seperation distance is very (very) small
            // Then we assume shapes are intersecting.
            axislsq = ((d0 * d0) + (d1 * d1) + (d2 * d2));
            if (axislsq <= WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                seperated = true;
                break;
            }

            // Prepare for next iteration.
            //VMath.v3Copy(newaxis, axis);
            axis0 = d0;
            axis1 = d1;
            axis2 = d2;

            // Check that we are getting closer with true distances
            // If not, terminate!
            var previousSqDistance = squaredDistance;
            squaredDistance = axislsq;

            if ((previousSqDistance - squaredDistance) <= (WebGLPhysicsConfig.GJK_FRACTIONAL_THRESHOLD * previousSqDistance)) {
                seperated = true;
                break;
            }

            if (curIter >= maxIter) {
                seperated = true;
                break;
            }

            if (this.numVertices === 4) {
                break;
            }
        }

        // If we cannot normalise axis, then necessarigly
        // seperated = false.
        // We do not zero the axis, as it is still useful enough for EPA.
        axislsq = ((axis0 * axis0) + (axis1 * axis1) + (axis2 * axis2));
        if (axislsq < WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            axis[0] = axis0;
            axis[1] = axis1;
            axis[2] = axis2;
            return undefined;
        }

        // Normalise axis whether GJK failed or succeeded:
        // Is useful information for futher investigations.
        var scale = 1 / Math.sqrt(axislsq);
        axis[0] = axis0 * scale;
        axis[1] = axis1 * scale;
        axis[2] = axis2 * scale;

        if (seperated) {
            // Get closest points in simplex.
            supportA[0] = closest[0];
            supportA[1] = closest[1];
            supportA[2] = closest[2];

            supportB[0] = closest[3];
            supportB[1] = closest[4];
            supportB[2] = closest[5];

            return Math.sqrt(squaredDistance);
        } else {
            return undefined;
        }
    };

    WebGLGJKContactSolver.create = function () {
        var solver = new WebGLGJKContactSolver();

        // current simplex with vertices W = P - Q, generated by points P and Q
        // [ W00 W01 W02 P01 P02 P03 Q01 Q02 Q03 ... ]
        solver.simplex = new Float32Array(36);
        solver.numVertices = 0;

        // on update closest points defined by W = P - Q stored here.
        solver.closest = new Float32Array(6);

        solver.cachedCoords = new Float32Array(4);
        solver.tempCoords = new Float32Array(4);

        return solver;
    };
    WebGLGJKContactSolver.version = 1;
    return WebGLGJKContactSolver;
})();

//
// WebGLContactEPA
//
var WebGLContactEPA = (function () {
    function WebGLContactEPA() {
    }
    WebGLContactEPA.prototype.bind = function (faceA, edgeA, faceB, edgeB) {
        faceA.edge[edgeA] = edgeB;
        faceA.adjFace[edgeA] = faceB;
        faceB.edge[edgeB] = edgeA;
        faceB.adjFace[edgeB] = faceA;
    };

    WebGLContactEPA.prototype.append = function (list, face) {
        face.leaf0 = null;
        face.leaf1 = list.root;
        if (list.root) {
            list.root.leaf0 = face;
        }
        list.root = face;
        list.count += 1;
    };

    WebGLContactEPA.prototype.remove = function (list, face) {
        var leaf0 = face.leaf0;
        var leaf1 = face.leaf1;
        if (leaf1) {
            leaf1.leaf0 = leaf0;
        }
        if (leaf0) {
            leaf0.leaf1 = leaf1;
        }
        if (face === list.root) {
            list.root = leaf1;
        }
        list.count -= 1;
    };

    WebGLContactEPA.prototype.findBest = function () {
        var minFace = this.hull.root;
        var minDistance = minFace.distance * minFace.distance;
        var f;
        for (f = minFace.leaf1; f !== null; f = f.leaf1) {
            var sqDistance = f.distance * f.distance;
            if (sqDistance < minDistance) {
                minFace = f;
                minDistance = sqDistance;
            }
        }

        return minFace;
    };

    WebGLContactEPA.prototype.getEdgeDistance = function (face, a, b) {
        var vertices = this.vertex_store;

        var a0 = vertices[a];
        var a1 = vertices[a + 1];
        var a2 = vertices[a + 2];

        var b0 = vertices[b];
        var b1 = vertices[b + 1];
        var b2 = vertices[b + 2];

        var ba0 = (b0 - a0);
        var ba1 = (b1 - a1);
        var ba2 = (b2 - a2);

        // outward facing edge normal on triangle plane
        var fn = face.normal;
        var fn0 = fn[0];
        var fn1 = fn[1];
        var fn2 = fn[2];

        var n0 = ((ba1 * fn2) - (ba2 * fn1));
        var n1 = ((ba2 * fn0) - (ba0 * fn2));
        var n2 = ((ba0 * fn1) - (ba1 * fn0));

        var dot = ((a0 * n0) + (a1 * n1) + (a2 * n2));
        if (dot <= 0) {
            //outside of edge A-B
            var lengthSqBA = ((ba0 * ba0) + (ba1 * ba1) + (ba2 * ba2));
            var dotA = ((a0 * ba0) + (a1 * ba1) + (a2 * ba2));
            var dotB = ((b0 * ba0) + (b1 * ba2) + (b2 * ba2));

            if (dotA >= 0) {
                //outside of vertex A
                return Math.sqrt((a0 * a0) + (a1 * a1) + (a2 * a2));
            } else if (dotB <= 0) {
                //outside of vertex B
                return Math.sqrt((b0 * b0) + (b1 * b1) + (b2 * b2));
            } else {
                var dotAB = ((a0 * b0) + (a1 * b1) + (a2 * b2));
                var dSq = (((a0 * a0) + (a1 * a1) + (a2 * a2)) * ((b0 * b0) + (b1 * b1) + (b2 * b2))) - (dotAB * dotAB);
                return dSq >= 0 ? Math.sqrt(dSq / lengthSqBA) : 0;
            }
        } else {
            return undefined;
        }
    };

    WebGLContactEPA.prototype.buildNewFace = function (a, b, c, forced) {
        var face = this.stock.root;
        if (face === null) {
            return null;
        }

        face.pass = 0;
        face.vertex[0] = a;
        face.vertex[1] = b;
        face.vertex[2] = c;

        var vertices = this.vertex_store;

        var a0 = vertices[a];
        var a1 = vertices[a + 1];
        var a2 = vertices[a + 2];

        var b0 = vertices[b];
        var b1 = vertices[b + 1];
        var b2 = vertices[b + 2];

        var c0 = vertices[c];
        var c1 = vertices[c + 1];
        var c2 = vertices[c + 2];

        var ba0 = (b0 - a0);
        var ba1 = (b1 - a1);
        var ba2 = (b2 - a2);

        var ca0 = (c0 - a0);
        var ca1 = (c1 - a1);
        var ca2 = (c2 - a2);

        var fn = face.normal;
        var fn0 = fn[0] = ((ba1 * ca2) - (ba2 * ca1));
        var fn1 = fn[1] = ((ba2 * ca0) - (ba0 * ca2));
        var fn2 = fn[2] = ((ba0 * ca1) - (ba1 * ca0));
        var length = ((fn0 * fn0) + (fn1 * fn1) + (fn2 * fn2));

        if (length > WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            face.distance = this.getEdgeDistance(face, a, b);

            if (face.distance === undefined) {
                face.distance = this.getEdgeDistance(face, b, c);
            }

            if (face.distance === undefined) {
                face.distance = this.getEdgeDistance(face, c, a);
            }

            var scale = 1 / Math.sqrt(length);
            if (face.distance === undefined) {
                // Origin must be closest to triangle plane.
                face.distance = ((a0 * fn0) + (a1 * fn1) + (a2 * fn2)) * scale;
            }

            if (forced || (face.distance >= -1e-6)) {
                fn[0] *= scale;
                fn[1] *= scale;
                fn[2] *= scale;

                // Success!
                this.remove(this.stock, face);
                this.append(this.hull, face);
                return face;
            }
        }

        return null;
    };

    WebGLContactEPA.prototype.expandFace = function (pass, w, face, edge, horizon) {
        if (face.pass !== pass) {
            var fn = face.normal;
            var fn0 = fn[0];
            var fn1 = fn[1];
            var fn2 = fn[2];

            var vertices = this.vertex_store;
            var w0 = vertices[w];
            var w1 = vertices[w + 1];
            var w2 = vertices[w + 2];

            var edge1 = (edge + 1) % 3;

            if ((((fn0 * w0) + (fn1 * w1) + (fn2 * w2)) - face.distance) < -1e-6) {
                var newFace = this.buildNewFace(face.vertex[edge1], face.vertex[edge], w, false);
                if (newFace) {
                    this.bind(newFace, 0, face, edge);
                    if (horizon.cf) {
                        this.bind(horizon.cf, 1, newFace, 2);
                    } else {
                        horizon.ff = newFace;
                    }
                    horizon.cf = newFace;
                    horizon.numFaces += 1;
                    return true;
                }
            } else {
                var edge2 = (edge + 2) % 3;
                face.pass = pass;
                if (this.expandFace(pass, w, face.adjFace[edge1], face.edge[edge1], horizon) && this.expandFace(pass, w, face.adjFace[edge2], face.edge[edge2], horizon)) {
                    this.remove(this.hull, face);
                    this.append(this.stock, face);
                    return true;
                }
            }
        }

        return false;
    };

    //
    // cache having properties
    //   shapeA
    //   shapeB
    //   axis <-- to be mutated by this function
    //     'on' object B.
    //   closestA <-- to be populated by this function
    //   closestB <-- to be populated by this function
    WebGLContactEPA.prototype.evaluate = function (gjkSimplex, cache, xformA, xformB) {
        var shapeA = cache.shapeA;
        var shapeB = cache.shapeB;

        var hull = this.hull;
        var stock = this.stock;

        while (hull.root) {
            var face = hull.root;
            this.remove(hull, face);
            this.append(stock, face);
        }

        // Orient simplex based on volume of tetrahedron
        var d0 = gjkSimplex[27];
        var d1 = gjkSimplex[28];
        var d2 = gjkSimplex[29];
        var ind0, ind1;

        var a0 = gjkSimplex[0] - d0;
        var a1 = gjkSimplex[1] - d1;
        var a2 = gjkSimplex[2] - d2;
        var b0 = gjkSimplex[9] - d0;
        var b1 = gjkSimplex[10] - d1;
        var b2 = gjkSimplex[11] - d2;
        var c0 = gjkSimplex[18] - d0;
        var c1 = gjkSimplex[19] - d1;
        var c2 = gjkSimplex[20] - d2;

        if (((a0 * ((b1 * c2) - (b2 * c1))) + (a1 * ((b2 * c0) - (b0 * c2))) + (a2 * ((b0 * c1) - (b1 * c0)))) < 0) {
            ind0 = 9;
            ind1 = 0;
        } else {
            ind0 = 0;
            ind1 = 9;
        }

        var vertices = this.vertex_store;
        var i;
        for (i = 0; i < 9; i += 1) {
            vertices[i] = gjkSimplex[ind0 + i];
            vertices[9 + i] = gjkSimplex[ind1 + i];
            vertices[18 + i] = gjkSimplex[18 + i];
            vertices[27 + i] = gjkSimplex[27 + i];
        }

        // Build initial convex hull
        var t0 = this.buildNewFace(0, 9, 18, true);
        var t1 = this.buildNewFace(9, 0, 27, true);
        var t2 = this.buildNewFace(18, 9, 27, true);
        var t3 = this.buildNewFace(0, 18, 27, true);

        var nextVertex = 36;

        if (hull.count !== 4) {
            VMath.v3Build(gjkSimplex[3], gjkSimplex[4], gjkSimplex[5], cache.closestA);
            VMath.v3Build(gjkSimplex[6], gjkSimplex[7], gjkSimplex[8], cache.closestB);
            return 0;
        }

        var best = this.findBest();
        var pass = 0;
        var iterations = 0;

        this.bind(t0, 0, t1, 0);
        this.bind(t0, 1, t2, 0);
        this.bind(t0, 2, t3, 0);
        this.bind(t1, 1, t3, 2);
        this.bind(t1, 2, t2, 1);
        this.bind(t2, 2, t3, 1);

        // Cached for frequent access.
        var A0 = xformA[0];
        var A1 = xformA[1];
        var A2 = xformA[2];
        var A3 = xformA[3];
        var A4 = xformA[4];
        var A5 = xformA[5];
        var A6 = xformA[6];
        var A7 = xformA[7];
        var A8 = xformA[8];
        var A9 = xformA[9];
        var A10 = xformA[10];
        var A11 = xformA[11];

        var B0 = xformB[0];
        var B1 = xformB[1];
        var B2 = xformB[2];
        var B3 = xformB[3];
        var B4 = xformB[4];
        var B5 = xformB[5];
        var B6 = xformB[6];
        var B7 = xformB[7];
        var B8 = xformB[8];
        var B9 = xformB[9];
        var B10 = xformB[10];
        var B11 = xformB[11];

        var supportA = cache.closestA;
        var supportB = cache.closestB;

        var horizon = this.horizon;
        var bn, n0, n1, n2;

        for (; iterations < 100; iterations += 1) {
            if (nextVertex >= this.MAX_VERTICES * 9) {
                break;
            }

            // reset horizon
            horizon.cf = horizon.ff = null;
            horizon.numFaces = 0;

            // get vertex from pool
            var w = nextVertex;
            nextVertex += 9;

            pass += 1;
            best.pass = pass;

            // populate vertex with supports.
            bn = best.normal;
            n0 = bn[0];
            n1 = bn[1];
            n2 = bn[2];

            //WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformVector(xformA, best.normal, supportA);
            //WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformVector(xformB, best.normal, supportB);
            //VMath.v3Neg(supportB, supportB);
            supportA[0] = ((A0 * n0) + (A1 * n1) + (A2 * n2));
            supportA[1] = ((A3 * n0) + (A4 * n1) + (A5 * n2));
            supportA[2] = ((A6 * n0) + (A7 * n1) + (A8 * n2));

            supportB[0] = -((B0 * n0) + (B1 * n1) + (B2 * n2));
            supportB[1] = -((B3 * n0) + (B4 * n1) + (B5 * n2));
            supportB[2] = -((B6 * n0) + (B7 * n1) + (B8 * n2));

            shapeA.localSupportWithoutMargin(supportA, supportA);
            shapeB.localSupportWithoutMargin(supportB, supportB);

            //VMath.m43TransformPoint(xformA, supportA, supportA);
            d0 = supportA[0];
            d1 = supportA[1];
            d2 = supportA[2];
            a0 = ((A0 * d0) + (A3 * d1) + (A6 * d2) + A9);
            a1 = ((A1 * d0) + (A4 * d1) + (A7 * d2) + A10);
            a2 = ((A2 * d0) + (A5 * d1) + (A8 * d2) + A11);

            //VMath.m43TransformPoint(xformB, supportB, supportB);
            d0 = supportB[0];
            d1 = supportB[1];
            d2 = supportB[2];
            b0 = ((B0 * d0) + (B3 * d1) + (B6 * d2) + B9);
            b1 = ((B1 * d0) + (B4 * d1) + (B7 * d2) + B10);
            b2 = ((B2 * d0) + (B5 * d1) + (B8 * d2) + B11);

            var w0, w1, w2;
            vertices[w + 3] = a0;
            vertices[w + 4] = a1;
            vertices[w + 5] = a2;
            vertices[w + 6] = b0;
            vertices[w + 7] = b1;
            vertices[w + 8] = b2;
            vertices[w] = w0 = (a0 - b0);
            vertices[w + 1] = w1 = (a1 - b1);
            vertices[w + 2] = w2 = (a2 - b2);

            // expand simplex
            var wDist = ((n0 * w0) + (n1 * w1) + (n2 * w2)) - best.distance;
            if (wDist > WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                var j;
                var valid = true;
                for (j = 0; (j < 3 && valid); j += 1) {
                    valid = valid && this.expandFace(pass, w, best.adjFace[j], best.edge[j], horizon);
                }

                if (valid && (horizon.numFaces >= 3)) {
                    this.bind(horizon.cf, 1, horizon.ff, 2);
                    this.remove(hull, best);
                    this.append(stock, best);
                    best = this.findBest();
                } else {
                    break;
                }
            } else {
                break;
            }
        }

        bn = best.normal;
        n0 = bn[0];
        n1 = bn[1];
        n2 = bn[2];
        var bd = best.distance;

        // Projection of origin onto final face of simplex.
        var p0 = n0 * bd;
        var p1 = n1 * bd;
        var p2 = n2 * bd;

        c0 = best.vertex[0];
        c1 = best.vertex[1];
        c2 = best.vertex[2];

        var x0 = vertices[c0] - p0;
        var x1 = vertices[c0 + 1] - p1;
        var x2 = vertices[c0 + 2] - p2;

        var y0 = vertices[c1] - p0;
        var y1 = vertices[c1 + 1] - p1;
        var y2 = vertices[c1 + 2] - p2;

        var z0 = vertices[c2] - p0;
        var z1 = vertices[c2 + 1] - p1;
        var z2 = vertices[c2 + 2] - p2;

        // Compute barycentric coordinates of origin's projection on face.
        d0 = ((y1 * z2) - (y2 * z1));
        d1 = ((y2 * z0) - (y0 * z2));
        d2 = ((y0 * z1) - (y1 * z0));
        var alpha = Math.sqrt((d0 * d0) + (d1 * d1) + (d2 * d2));

        d0 = ((z1 * x2) - (z2 * x1));
        d1 = ((z2 * x0) - (z0 * x2));
        d2 = ((z0 * x1) - (z1 * x0));
        var beta = Math.sqrt((d0 * d0) + (d1 * d1) + (d2 * d2));

        d0 = ((x1 * y2) - (x2 * y1));
        d1 = ((x2 * y0) - (x0 * y2));
        d2 = ((x0 * y1) - (x1 * y0));
        var gamma = Math.sqrt((d0 * d0) + (d1 * d1) + (d2 * d2));

        var scale = 1 / (alpha + beta + gamma);
        alpha *= scale;
        beta *= scale;
        gamma *= scale;

        // Interpolate for ideal support points.
        supportA[0] = supportA[1] = supportA[2] = 0;
        supportB[0] = supportB[1] = supportB[2] = 0;
        for (i = 0; i < 3; i += 1) {
            supportA[i] += (alpha * vertices[c0 + 3 + i]) + (beta * vertices[c1 + 3 + i]) + (gamma * vertices[c2 + 3 + i]);
            supportB[i] += (alpha * vertices[c0 + 6 + i]) + (beta * vertices[c1 + 6 + i]) + (gamma * vertices[c2 + 6 + i]);
        }

        var axis = cache.axis;
        axis[0] = -n0;
        axis[1] = -n1;
        axis[2] = -n2;
        return (-best.distance);
    };

    WebGLContactEPA.create = function () {
        var epa = new WebGLContactEPA();
        var i;

        // populate vertex and face pools
        epa.vertex_store = new Float32Array(epa.MAX_VERTICES * 9);

        var face_store = [];
        for (i = 0; i < epa.MAX_FACES; i += 1) {
            face_store[i] = {
                normal: VMath.v3BuildZero(),
                distance: 0,
                vertex: new Int16Array(3),
                adjFace: [null, null, null],
                edge: new Int16Array(3),
                leaf0: null,
                leaf1: null,
                pass: 0
            };
        }

        // initialise hull, stock and horizon
        epa.hull = {
            root: null,
            count: 0
        };

        epa.stock = {
            root: null,
            count: 0
        };

        epa.horizon = {
            cf: null,
            ff: null,
            numFaces: 0
        };

        for (i = 0; i < epa.MAX_FACES; i += 1) {
            epa.append(epa.stock, face_store[epa.MAX_FACES - i - 1]);
        }

        return epa;
    };
    WebGLContactEPA.version = 1;
    return WebGLContactEPA;
})();

WebGLContactEPA.prototype.MAX_VERTICES = 64;
WebGLContactEPA.prototype.MAX_FACES = 128;

//
// WebGLPhysicsPublicContact
//
var WebGLPhysicsPublicContact = (function () {
    // members?
    function WebGLPhysicsPublicContact() {
        this._private = null;
        return this;
    }
    WebGLPhysicsPublicContact.create = function () {
        var p = new WebGLPhysicsPublicContact();

        Object.defineProperty(p, "localPointOnA", {
            get: function getLocalPointOnA() {
                var pr = this._private;
                return VMath.v3Build(pr[0], pr[1], pr[2]);
            },
            set: function setLocalPointOnA(point) {
                var pr = this._private;
                pr[0] = point[0];
                pr[1] = point[1];
                pr[2] = point[2];
            },
            enumerable: true
        });

        Object.defineProperty(p, "localPointOnB", {
            get: function getLocalPointOnB() {
                var pr = this._private;
                return VMath.v3Build(pr[3], pr[4], pr[5]);
            },
            set: function setLocalPointOnB(point) {
                var pr = this._private;
                pr[3] = point[0];
                pr[4] = point[1];
                pr[5] = point[2];
            },
            enumerable: true
        });

        Object.defineProperty(p, "worldNormalOnB", {
            get: function getWorldNormalOnB() {
                var pr = this._private;
                return VMath.v3Build(pr[12], pr[13], pr[14]);
            },
            set: function setWorldNormalOnB(normal) {
                var pr = this._private;
                pr[12] = normal[0];
                pr[13] = normal[1];
                pr[14] = normal[2];
            },
            enumerable: true
        });

        Object.defineProperty(p, "added", {
            get: function getAdded() {
                var pr = this._private;
                return (0.0 < pr[51]);
            },
            enumerable: true
        });

        Object.defineProperty(p, "distance", {
            get: function getDistance() {
                var pr = this._private;
                return pr[21];
            },
            enumerable: true
        });

        return p;
    };
    return WebGLPhysicsPublicContact;
})();

var WebGLPhysicsContact = {
    contactPool: [],
    contactPoolSize: 0,
    contactPoolAllocationSize: 128,
    publicContacts: [
        WebGLPhysicsPublicContact.create(),
        WebGLPhysicsPublicContact.create(),
        WebGLPhysicsPublicContact.create()
    ],
    callbackContacts: [],
    allocate: function webglPhyssicsContactAllocateFn() {
        var contactPool = this.contactPool;

        if (this.contactPoolSize === 0) {
            var allocationSize = this.contactPoolAllocationSize;
            var buffer = new Float32Array(52 * allocationSize);
            var bufferIndex = buffer.length;
            var n;
            for (n = 0; n < allocationSize; n += 1) {
                bufferIndex -= 52;
                contactPool[n] = buffer.subarray(bufferIndex, (bufferIndex + 52));
            }
            this.contactPoolSize = allocationSize;
        }

        this.contactPoolSize -= 1;
        var contact = contactPool[this.contactPoolSize];

        contact[51] = 1.0;

        return contact;
    },
    deallocate: function webglPhyssicsContactDeallocateFn(contact) {
        this.contactPool[this.contactPoolSize] = contact;
        this.contactPoolSize += 1;

        // Contact jAccN is cached between updates. Needs to be reset if contact is re-used.
        contact[40] = 0;
    }
};

//
// WebGLPhysicsArbiter
//
var WebGLPhysicsArbiter = (function () {
    function WebGLPhysicsArbiter() {
        // Initialise all properties of arbiters
        // which will ever be used.
        // Object between which this contact patch is defined.
        // Precondition: objectA.id < objectB.id
        this.objectA = null;
        this.objectB = null;

        // Shapes between which this contact patch is defined.
        // Precondition: object#.shape = shape#
        this.shapeA = null;
        this.shapeB = null;

        // Pairwise friction and restitution values.
        this.friction = 0;
        this.restitution = 0;

        // Set of contacts in patch.
        this.contacts = [];

        // Set of contacts with negative distance for physics computaions.
        this.activeContacts = [];

        // Whether contact is active (As compared to being sleeping).
        this.active = true;

        // Flag used to ignore unneccesary discrete collision checks in post-continuous collisions.
        this.skipDiscreteCollisions = false;

        // Flags to signal processing of contact callbacks
        this.contactFlags = 0;

        // Flag to disable contact response
        this.trigger = false;

        return this;
    }
    WebGLPhysicsArbiter.prototype.insertContact = function (worldA, worldB, normal, distance, concave) {
        var cn0 = normal[0];
        var cn1 = normal[1];
        var cn2 = normal[2];
        var clsq = ((cn0 * cn0) + (cn1 * cn1) + (cn2 * cn2));

        if (clsq < WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            return;
        }

        var scale = 1 / Math.sqrt(clsq);
        cn0 *= scale;
        cn1 *= scale;
        cn2 *= scale;

        //WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformPoint(this.objectA.transform, worldA, c.localA);
        //WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformPoint(this.objectB.transform, worldB, c.localB);
        //var localA = c.localA;
        //var localB = c.localB;
        //var relA = c.relA;
        //var relB = c.relB;
        var objectA = this.objectA;
        var objectB = this.objectB;
        var xformA = objectA.transform;
        var xformB = objectB.transform;
        var r0 = worldA[0] - xformA[9];
        var r1 = worldA[1] - xformA[10];
        var r2 = worldA[2] - xformA[11];
        var ca0 = (xformA[0] * r0) + (xformA[1] * r1) + (xformA[2] * r2);
        var ca1 = (xformA[3] * r0) + (xformA[4] * r1) + (xformA[5] * r2);
        var ca2 = (xformA[6] * r0) + (xformA[7] * r1) + (xformA[8] * r2);
        var jAccN = 0;

        // Cull any contacts with different normal
        // Inherit accumulated impulse of nearby contact
        /*jshint bitwise: false*/
        var i = 0;
        var min = Number.MAX_VALUE;
        var contacts = this.contacts;
        var d0, d1, d2;
        while (i < contacts.length) {
            var datad = contacts[i];

            if ((!concave) && ((cn0 * datad[12]) + (cn1 * datad[13]) + (cn2 * datad[14])) < 0.9) {
                contacts[i] = contacts[contacts.length - 1];
                contacts.pop();
                WebGLPhysicsContact.deallocate(datad);
                this.contactFlags |= 4;
                continue;
            }

            //var dlocalA = d.localA;
            d0 = (ca0 - datad[0]);
            d1 = (ca1 - datad[1]);
            d2 = (ca2 - datad[2]);
            var sep = (d0 * d0) + (d1 * d1) + (d2 * d2);
            if (sep < WebGLPhysicsConfig.CONTACT_EQUAL_SQ_SEPERATION) {
                //c.jAccN = d.jAccN;
                jAccN = datad[40];
                contacts[i] = contacts[contacts.length - 1];
                contacts.pop();
                WebGLPhysicsContact.deallocate(datad);
                this.contactFlags |= 4;
                min = sep;
                continue;
            }

            if (sep < WebGLPhysicsConfig.CONTACT_INHERIT_SQ_SEPERATION && sep < min) {
                //c.jAccN = d.jAccN;
                jAccN = datad[40];
                min = sep;
            }

            i += 1;
        }

        var data = WebGLPhysicsContact.allocate();

        data[0] = ca0;
        data[1] = ca1;
        data[2] = ca2;
        data[6] = r0;
        data[7] = r1;
        data[8] = r2;

        data[9] = r0 = worldB[0] - xformB[9];
        data[10] = r1 = worldB[1] - xformB[10];
        data[11] = r2 = worldB[2] - xformB[11];
        data[3] = (xformB[0] * r0) + (xformB[1] * r1) + (xformB[2] * r2);
        data[4] = (xformB[3] * r0) + (xformB[4] * r1) + (xformB[5] * r2);
        data[5] = (xformB[6] * r0) + (xformB[7] * r1) + (xformB[8] * r2);

        //c.distance = distance;
        data[21] = distance;

        // contact normal, normalised.
        //var basis = c.basis;
        data[12] = cn0;
        data[13] = cn1;
        data[14] = cn2;

        // contact tangent.
        var ct0, ct2;
        clsq = ((cn0 * cn0) + (cn2 * cn2));
        if (clsq < WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            data[15] = ct0 = 1.0;
            data[16] = 0.0;
            data[17] = ct2 = 0.0;
        } else {
            scale = 1 / Math.sqrt(clsq);
            data[15] = ct0 = (-cn2 * scale);
            data[16] = 0.0;
            data[17] = ct2 = (cn0 * scale);
        }

        // contact bitangent
        data[18] = ((cn1 * ct2));
        data[19] = ((cn2 * ct0) - (cn0 * ct2));
        data[20] = (-(cn1 * ct0));

        data[40] = jAccN;

        var contactCallbacks, publicContact;
        contactCallbacks = objectA.contactCallbacks;
        if (null !== contactCallbacks && 0 !== (contactCallbacks.mask & objectB.group)) {
            if (contactCallbacks.onPreSolveContact) {
                publicContact = WebGLPhysicsContact.publicContacts[0];
                publicContact._private = data;
                contactCallbacks.onPreSolveContact(objectA._public, objectB._public, publicContact);
            }
            if (!contactCallbacks.added && contactCallbacks.deferred) {
                contactCallbacks.added = true;
                objectA.world.contactCallbackObjects.push(objectA);
            }
            if (contactCallbacks.trigger) {
                this.trigger = true;
                objectA.sweepFrozen = false;
                objectB.sweepFrozen = false;
            }
        }
        contactCallbacks = objectB.contactCallbacks;
        if (null !== contactCallbacks && 0 !== (contactCallbacks.mask & objectA.group)) {
            if (contactCallbacks.onPreSolveContact) {
                publicContact = WebGLPhysicsContact.publicContacts[0];
                publicContact._private = data;
                contactCallbacks.onPreSolveContact(objectA._public, objectB._public, publicContact);
            }
            if (!contactCallbacks.added && contactCallbacks.deferred) {
                contactCallbacks.added = true;
                objectB.world.contactCallbackObjects.push(objectB);
            }
            if (contactCallbacks.trigger) {
                this.trigger = true;
                objectA.sweepFrozen = false;
                objectB.sweepFrozen = false;
            }
        }

        this.contactFlags |= 1;

        contacts.push(data);

        if (contacts.length === 4) {
            // Discard one contact, so that remaining 3 have maximum area, and contain deepest contact
            // Find deepest.
            var minDistance = contacts[0][21];
            var minimum = 0;
            for (i = 1; i < 4; i += 1) {
                data = contacts[i];
                if (data[21] < minDistance) {
                    minDistance = data[21];
                    minimum = i;
                }
            }

            var discard;
            var maxArea = -Number.MAX_VALUE;

            var con0 = contacts[0];
            var con1 = contacts[1];
            var con2 = contacts[2];
            var con3 = contacts[3];

            // World coordinates of contact points (Scaled and translated, but does not matter).
            var a0 = con0[6] + con0[9];
            var a1 = con0[7] + con0[10];
            var a2 = con0[8] + con0[11];

            var b0 = con1[6] + con1[9];
            var b1 = con1[7] + con1[10];
            var b2 = con1[8] + con1[11];

            var c0 = con2[6] + con2[9];
            var c1 = con2[7] + con2[10];
            var c2 = con2[8] + con2[11];

            d0 = con3[6] + con3[9];
            d1 = con3[7] + con3[10];
            d2 = con3[8] + con3[11];

            var ab0 = (b0 - a0);
            var ab1 = (b1 - a1);
            var ab2 = (b2 - a2);

            var ac0 = (c0 - a0);
            var ac1 = (c1 - a1);
            var ac2 = (c2 - a2);

            var ad0 = (d0 - a0);
            var ad1 = (d1 - a1);
            var ad2 = (d2 - a2);

            var n0, n1, n2;
            var area;

            if (minimum !== 1) {
                n0 = ((ac1 * ad2) - (ac2 * ad1));
                n1 = ((ac2 * ad0) - (ac0 * ad2));
                n2 = ((ac0 * ad1) - (ac1 * ad0));
                area = (n0 * n0) + (n1 * n1) + (n2 * n2);
                if (area > maxArea) {
                    maxArea = area;
                    discard = 1;
                }
            }

            if (minimum !== 2) {
                n0 = ((ab1 * ad2) - (ab2 * ad1));
                n1 = ((ab2 * ad0) - (ab0 * ad2));
                n2 = ((ab0 * ad1) - (ab1 * ad0));
                area = (n0 * n0) + (n1 * n1) + (n2 * n2);
                if (area > maxArea) {
                    maxArea = area;
                    discard = 2;
                }
            }

            if (minimum !== 3) {
                n0 = ((ab1 * ac2) - (ab2 * ac1));
                n1 = ((ab2 * ac0) - (ab0 * ac2));
                n2 = ((ab0 * ac1) - (ab1 * ac0));
                area = (n0 * n0) + (n1 * n1) + (n2 * n2);
                if (area > maxArea) {
                    maxArea = area;
                    discard = 3;
                }
            }

            if (minimum !== 0) {
                var bc0 = (c0 - b0);
                var bc1 = (c1 - b1);
                var bc2 = (c2 - b2);

                var bd0 = (d0 - b0);
                var bd1 = (d1 - b1);
                var bd2 = (d2 - b2);

                n0 = ((bc1 * bd2) - (bc2 * bd1));
                n1 = ((bc2 * bd0) - (bc0 * bd2));
                n2 = ((bc0 * bd1) - (bc1 * bd0));
                area = (n0 * n0) + (n1 * n1) + (n2 * n2);
                if (area > maxArea) {
                    maxArea = area;
                    discard = 0;
                }
            }

            data = contacts[discard];
            contacts[discard] = contacts[3];
            contacts.pop();
            WebGLPhysicsContact.deallocate(data);
            this.contactFlags |= 4;
        }
        /*jshint bitwise: true*/
    };

    WebGLPhysicsArbiter.prototype.refreshContacts = function () {
        var contacts = this.contacts;
        var objectA = this.objectA;
        var objectB = this.objectB;

        var xformA = objectA.transform;
        var xformB = objectB.transform;

        // Cached for use throughout method.
        var A0 = xformA[0];
        var A1 = xformA[1];
        var A2 = xformA[2];
        var A3 = xformA[3];
        var A4 = xformA[4];
        var A5 = xformA[5];
        var A6 = xformA[6];
        var A7 = xformA[7];
        var A8 = xformA[8];
        var A9 = xformA[9];
        var A10 = xformA[10];
        var A11 = xformA[11];

        var B0 = xformB[0];
        var B1 = xformB[1];
        var B2 = xformB[2];
        var B3 = xformB[3];
        var B4 = xformB[4];
        var B5 = xformB[5];
        var B6 = xformB[6];
        var B7 = xformB[7];
        var B8 = xformB[8];
        var B9 = xformB[9];
        var B10 = xformB[10];
        var B11 = xformB[11];

        var data;
        var i = 0;
        while (i < contacts.length) {
            data = contacts[i];

            //VMath.m43TransformVector(this.objectA.transform, c.localA, c.relA);
            var v0 = data[0];
            var v1 = data[1];
            var v2 = data[2];
            var ra0 = data[6] = ((A0 * v0) + (A3 * v1) + (A6 * v2));
            var ra1 = data[7] = ((A1 * v0) + (A4 * v1) + (A7 * v2));
            var ra2 = data[8] = ((A2 * v0) + (A5 * v1) + (A8 * v2));

            //VMath.m43TransformVector(this.objectB.transform, c.localB, c.relB);
            v0 = data[3];
            v1 = data[4];
            v2 = data[5];
            var rb0 = data[9] = ((B0 * v0) + (B3 * v1) + (B6 * v2));
            var rb1 = data[10] = ((B1 * v0) + (B4 * v1) + (B7 * v2));
            var rb2 = data[11] = ((B2 * v0) + (B5 * v1) + (B8 * v2));

            // contact seperation.
            v0 = (ra0 + A9) - (rb0 + B9);
            v1 = (ra1 + A10) - (rb1 + B10);
            v2 = (ra2 + A11) - (rb2 + B11);

            //var basis = c.basis;
            var n0 = data[12];
            var n1 = data[13];
            var n2 = data[14];

            //c.distance = VMath.v3Dot(c.normal, seperation);
            var sep = data[21] = ((n0 * v0) + (n1 * v1) + (n2 * v2));
            if (sep > WebGLPhysicsConfig.CONTACT_MAX_Y_SEPERATION) {
                contacts[i] = contacts[contacts.length - 1];
                contacts.pop();
                WebGLPhysicsContact.deallocate(data);
                this.contactFlags |= 4;
                continue;
            }

            //VMath.v3AddScalarMul(seperation, c.normal, -c.distance, seperation);
            v0 -= (n0 * sep);
            v1 -= (n1 * sep);
            v2 -= (n2 * sep);

            if (((v0 * v0) + (v1 * v1) + (v2 * v2)) > WebGLPhysicsConfig.CONTACT_MAX_SQ_XZ_SEPERATION) {
                contacts[i] = contacts[contacts.length - 1];
                contacts.pop();
                WebGLPhysicsContact.deallocate(data);
                this.contactFlags |= 4;
                continue;
            }

            i += 1;
        }

        this.contactFlags |= 2;

        return (contacts.length === 0);
    };

    WebGLPhysicsArbiter.prototype.preStep = function (timeStepRatio, timeStep) {
        if (this.trigger) {
            this.activeContacts.length = 0;
            return;
        }

        var objectA = this.objectA;
        var objectB = this.objectB;
        var mass_sum = objectA.inverseMass + objectB.inverseMass;

        var velA = objectA.velocity;
        var velB = objectB.velocity;

        // cached for frequent access.
        var I = objectA.inverseInertia;
        var A0 = I[0];
        var A1 = I[1];
        var A2 = I[2];
        var A3 = I[3];
        var A4 = I[4];
        var A5 = I[5];
        var A6 = I[6];
        var A7 = I[7];
        var A8 = I[8];

        I = objectB.inverseInertia;
        var B0 = I[0];
        var B1 = I[1];
        var B2 = I[2];
        var B3 = I[3];
        var B4 = I[4];
        var B5 = I[5];
        var B6 = I[6];
        var B7 = I[7];
        var B8 = I[8];

        var activeContacts = this.activeContacts;
        activeContacts.length = 0;

        // TOOD: REMOVE the <any> casts.  objectA appears to be a
        // WebGLPhysicsCollisionObject.  Does that have a
        // 'collisionObject' property?
        var baum = ((objectA).collisionObject || (objectB).collisionObject) ? WebGLPhysicsConfig.CONTACT_STATIC_BAUMGRAUTE : WebGLPhysicsConfig.CONTACT_BAUMGRAUTE;

        var contacts = this.contacts;
        var i;
        var limit = contacts.length;
        for (i = 0; i < limit; i += 1) {
            var data = contacts[i];
            if (data[21] > 0) {
                continue;
            }

            // TODO: remove this cast and fix the type error
            activeContacts[activeContacts.length] = data;

            // cacheing friction impulses between steps
            // caused them to fight eachother instead of stabalising at 0.
            data[41] = data[42] = 0;

            var ca0, ca1, ca2;
            var cb0, cb1, cb2;

            //var basis = c.basis;
            var n0 = data[12];
            var n1 = data[13];
            var n2 = data[14];

            var ra0 = data[6];
            var ra1 = data[7];
            var ra2 = data[8];

            var rb0 = data[9];
            var rb1 = data[10];
            var rb2 = data[11];

            //var jac = c.jac;
            var k0, k1, k2;

            // Compute effective mass and jacobian of penetration constraint.
            var kN = mass_sum;

            //crossA = VMath.v3Cross(c.relA, c.normal);
            ca0 = ((ra1 * n2) - (ra2 * n1));
            ca1 = ((ra2 * n0) - (ra0 * n2));
            ca2 = ((ra0 * n1) - (ra1 * n0));

            //c.nCrossA = VMath.m33Transform(objectA.inverseInertia, crossA);
            data[22] = k0 = ((A0 * ca0) + (A3 * ca1) + (A6 * ca2));
            data[23] = k1 = ((A1 * ca0) + (A4 * ca1) + (A7 * ca2));
            data[24] = k2 = ((A2 * ca0) + (A5 * ca1) + (A8 * ca2));
            kN += ((ca0 * k0) + (ca1 * k1) + (ca2 * k2));

            //crossB = VMbth.v3Cross(c.relB, c.normal);
            cb0 = ((rb1 * n2) - (rb2 * n1));
            cb1 = ((rb2 * n0) - (rb0 * n2));
            cb2 = ((rb0 * n1) - (rb1 * n0));

            //c.nCrossB = VMbth.m33Trbnsform(objectB.inverseInertib, crossB);
            data[25] = k0 = -((B0 * cb0) + (B3 * cb1) + (B6 * cb2));
            data[26] = k1 = -((B1 * cb0) + (B4 * cb1) + (B7 * cb2));
            data[27] = k2 = -((B2 * cb0) + (B5 * cb1) + (B8 * cb2));
            kN -= ((cb0 * k0) + (cb1 * k1) + (cb2 * k2));

            data[45] = 1 / kN;

            // Compute positional bias for baumgraute stabalisation#
            data[43] = baum * Math.min(0, data[21] + WebGLPhysicsConfig.CONTACT_SLOP) / timeStep;
            data[44] = 0;

            // Compute velocity at contact
            // var vel = VMath.v3Sub(velA, velB);
            var vel0 = (velA[0] - velB[0]);
            var vel1 = (velA[1] - velB[1]);
            var vel2 = (velA[2] - velB[2]);

            // vel += VMath.v3Cross(angA, c.relA);
            vel0 += ((velA[4] * ra2) - (velA[5] * ra1));
            vel1 += ((velA[5] * ra0) - (velA[3] * ra2));
            vel2 += ((velA[3] * ra1) - (velA[4] * ra0));

            // vel -= VMath.v3Cross(velB, c.relB);
            vel0 -= ((velB[4] * rb2) - (velB[5] * rb1));
            vel1 -= ((velB[5] * rb0) - (velB[3] * rb2));
            vel2 -= ((velB[3] * rb1) - (velB[4] * rb0));

            // Compute bounce bias.
            //c.bounce = VMath.v3Dot(vel, c.normal) * this.restitution;
            var bounce = ((vel0 * n0) + (vel1 * n1) + (vel2 * n2)) * this.restitution;

            if (bounce * bounce < 1e-2) {
                bounce = 0;
            }
            data[50] = bounce;

            // Compute effective mass and jacobian of friction constraint.
            var kU = mass_sum;
            n0 = data[15];
            n1 = data[16];
            n2 = data[17];

            //crossA = VMath.v3Cross(c.relA, c.tangent);
            ca0 = ((ra1 * n2) - (ra2 * n1));
            ca1 = ((ra2 * n0) - (ra0 * n2));
            ca2 = ((ra0 * n1) - (ra1 * n0));

            //c.uCrossA = VMath.m33Transform(objecnA.inverseInertia, crossA);
            data[28] = k0 = ((A0 * ca0) + (A3 * ca1) + (A6 * ca2));
            data[29] = k1 = ((A1 * ca0) + (A4 * ca1) + (A7 * ca2));
            data[30] = k2 = ((A2 * ca0) + (A5 * ca1) + (A8 * ca2));
            kU += ((ca0 * k0) + (ca1 * k1) + (ca2 * k2));

            //crossB = VMbth.v3Cross(c.relB, c.tangent);
            cb0 = ((rb1 * n2) - (rb2 * n1));
            cb1 = ((rb2 * n0) - (rb0 * n2));
            cb2 = ((rb0 * n1) - (rb1 * n0));

            //c.uCrossB = VMbth.m33Trbnsform(objecnB.inverseInertib, crossB);
            data[31] = k0 = -((B0 * cb0) + (B3 * cb1) + (B6 * cb2));
            data[32] = k1 = -((B1 * cb0) + (B4 * cb1) + (B7 * cb2));
            data[33] = k2 = -((B2 * cb0) + (B5 * cb1) + (B8 * cb2));
            kU -= ((cb0 * k0) + (cb1 * k1) + (cb2 * k2));

            var kV = mass_sum;
            n0 = data[18];
            n1 = data[19];
            n2 = data[20];

            //crossA = VMath.v3Cross(c.relA, c.bitangent);
            ca0 = ((ra1 * n2) - (ra2 * n1));
            ca1 = ((ra2 * n0) - (ra0 * n2));
            ca2 = ((ra0 * n1) - (ra1 * n0));

            //c.vCrossA = VMath.m33Transform(objecnA.inverseInertia, crossA);
            data[34] = k0 = ((A0 * ca0) + (A3 * ca1) + (A6 * ca2));
            data[35] = k1 = ((A1 * ca0) + (A4 * ca1) + (A7 * ca2));
            data[36] = k2 = ((A2 * ca0) + (A5 * ca1) + (A8 * ca2));
            kV += ((ca0 * k0) + (ca1 * k1) + (ca2 * k2));

            //crossB = VMbth.v3Cross(c.relB, c.bitangent);
            cb0 = ((rb1 * n2) - (rb2 * n1));
            cb1 = ((rb2 * n0) - (rb0 * n2));
            cb2 = ((rb0 * n1) - (rb1 * n0));

            //c.vCrossB = VMbth.m33Trbnsform(objecnB.inverseInertib, crossB);
            data[37] = k0 = -((B0 * cb0) + (B3 * cb1) + (B6 * cb2));
            data[38] = k1 = -((B1 * cb0) + (B4 * cb1) + (B7 * cb2));
            data[39] = k2 = -((B2 * cb0) + (B5 * cb1) + (B8 * cb2));
            kV -= ((cb0 * k0) + (cb1 * k1) + (cb2 * k2));

            var kUV = 0.0;
            kUV += ((ca0 * data[28]) + (ca1 * data[29]) + (ca2 * data[30]));
            kUV -= ((cb0 * data[31]) + (cb1 * data[32]) + (cb2 * data[33]));

            var idet = 1 / (kU * kV - kUV * kUV);
            data[46] = kV * idet;
            data[47] = -kUV * idet;
            data[48] = kU * idet;

            // scale cached impulse for change in time step
            data[40] *= timeStepRatio;
        }
    };

    WebGLPhysicsArbiter.prototype.applyCachedImpulses = function () {
        if (this.trigger) {
            return;
        }

        var objectA = this.objectA;
        var objectB = this.objectB;

        var velA = objectA.velocity;
        var velB = objectB.velocity;

        var imA = objectA.inverseMass;
        var imB = objectB.inverseMass;

        var contacts = this.activeContacts;
        var i;
        for (i = 0; i < contacts.length; i += 1) {
            var data = contacts[i];

            var jn = data[40];
            var n0 = (data[12] * jn);
            var n1 = (data[13] * jn);
            var n2 = (data[14] * jn);

            velA[0] += (n0 * imA);
            velA[1] += (n1 * imA);
            velA[2] += (n2 * imA);

            velB[0] -= (n0 * imB);
            velB[1] -= (n1 * imB);
            velB[2] -= (n2 * imB);

            velA[3] += (data[22] * jn);
            velA[4] += (data[23] * jn);
            velA[5] += (data[24] * jn);

            velB[3] += (data[25] * jn);
            velB[4] += (data[26] * jn);
            velB[5] += (data[27] * jn);
        }
    };

    WebGLPhysicsArbiter.prototype.computeAndApplyBiasImpulses = function () {
        if (this.trigger) {
            return;
        }

        var objectA = this.objectA;
        var objectB = this.objectB;

        // Set velocities to local vars.
        var vec = objectA.velocity;
        var va0 = vec[6];
        var va1 = vec[7];
        var va2 = vec[8];
        var wa0 = vec[9];
        var wa1 = vec[10];
        var wa2 = vec[11];

        vec = objectB.velocity;
        var vb0 = vec[6];
        var vb1 = vec[7];
        var vb2 = vec[8];
        var wb0 = vec[9];
        var wb1 = vec[10];
        var wb2 = vec[11];

        var imA = objectA.inverseMass;
        var imB = objectB.inverseMass;

        var contacts = this.activeContacts;
        var limit = contacts.length;
        var data;
        var i;
        for (i = 0; i < limit; i += 1) {
            data = contacts[i];

            var n0 = data[12];
            var n1 = data[13];
            var n2 = data[14];

            var ra0 = data[6];
            var ra1 = data[7];
            var ra2 = data[8];

            var rb0 = data[9];
            var rb1 = data[10];
            var rb2 = data[11];

            // Velocity normal impulse.
            var j1 = data[45] * (n0 * ((vb0 + ((wb1 * rb2) - (wb2 * rb1))) - (va0 + ((wa1 * ra2) - (wa2 * ra1)))) + n1 * ((vb1 + ((wb2 * rb0) - (wb0 * rb2))) - (va1 + ((wa2 * ra0) - (wa0 * ra2)))) + n2 * ((vb2 + ((wb0 * rb1) - (wb1 * rb0))) - (va2 + ((wa0 * ra1) - (wa1 * ra0)))) - data[43]);

            // Accumulate and clamp.
            var jOld1 = data[44];
            var cjAcc1 = jOld1 + j1;
            if (cjAcc1 < 0) {
                cjAcc1 = 0.0;
            }
            j1 = cjAcc1 - jOld1;
            data[44] = cjAcc1;

            // Apply normal impulse.
            n0 *= j1;
            n1 *= j1;
            n2 *= j1;

            va0 += (n0 * imA);
            va1 += (n1 * imA);
            va2 += (n2 * imA);

            vb0 -= (n0 * imB);
            vb1 -= (n1 * imB);
            vb2 -= (n2 * imB);

            wa0 += (data[22] * j1);
            wa1 += (data[23] * j1);
            wa2 += (data[24] * j1);

            wb0 += (data[25] * j1);
            wb1 += (data[26] * j1);
            wb2 += (data[27] * j1);
        }

        // Set local vars to velocities.
        vec = objectA.velocity;
        vec[6] = va0;
        vec[7] = va1;
        vec[8] = va2;
        vec[9] = wa0;
        vec[10] = wa1;
        vec[11] = wa2;

        vec = objectB.velocity;
        vec[6] = vb0;
        vec[7] = vb1;
        vec[8] = vb2;
        vec[9] = wb0;
        vec[10] = wb1;
        vec[11] = wb2;
    };

    WebGLPhysicsArbiter.prototype.computeAndApplyImpulses = function () {
        if (this.trigger) {
            return;
        }

        var objectA = this.objectA;
        var objectB = this.objectB;

        // Set velocities to local vars.
        var vec = objectA.velocity;
        var va0 = vec[0];
        var va1 = vec[1];
        var va2 = vec[2];
        var wa0 = vec[3];
        var wa1 = vec[4];
        var wa2 = vec[5];

        vec = objectB.velocity;
        var vb0 = vec[0];
        var vb1 = vec[1];
        var vb2 = vec[2];
        var wb0 = vec[3];
        var wb1 = vec[4];
        var wb2 = vec[5];

        var imA = objectA.inverseMass;
        var imB = objectB.inverseMass;

        var friction = this.friction;

        var contacts = this.activeContacts;
        var limit = contacts.length;
        var data;
        var i;
        for (i = 0; i < limit; i += 1) {
            data = contacts[i];

            var n0 = data[12];
            var n1 = data[13];
            var n2 = data[14];
            var u0 = data[15];
            var u1 = data[16];
            var u2 = data[17];
            var v0 = data[18];
            var v1 = data[19];
            var v2 = data[20];

            var ra0 = data[6];
            var ra1 = data[7];
            var ra2 = data[8];

            var rb0 = data[9];
            var rb1 = data[10];
            var rb2 = data[11];

            // Velocity normal impulse.
            var j1 = data[45] * (n0 * ((vb0 + ((wb1 * rb2) - (wb2 * rb1))) - (va0 + ((wa1 * ra2) - (wa2 * ra1)))) + n1 * ((vb1 + ((wb2 * rb0) - (wb0 * rb2))) - (va1 + ((wa2 * ra0) - (wa0 * ra2)))) + n2 * ((vb2 + ((wb0 * rb1) - (wb1 * rb0))) - (va2 + ((wa0 * ra1) - (wa1 * ra0)))) - data[50]);

            // Accumulate and clamp.
            var jOld1 = data[40];
            var cjAcc1 = jOld1 + j1;
            if (cjAcc1 < 0) {
                cjAcc1 = 0.0;

                //j1 = cjAcc1 - jOld1;
                j1 = -jOld1;
            }
            data[40] = cjAcc1;

            // Apply normal impulse.
            n0 *= j1;
            n1 *= j1;
            n2 *= j1;

            va0 += (n0 * imA);
            va1 += (n1 * imA);
            va2 += (n2 * imA);

            vb0 -= (n0 * imB);
            vb1 -= (n1 * imB);
            vb2 -= (n2 * imB);

            wa0 += (data[22] * j1);
            wa1 += (data[23] * j1);
            wa2 += (data[24] * j1);

            wb0 += (data[25] * j1);
            wb1 += (data[26] * j1);
            wb2 += (data[27] * j1);

            // Relative velocity at contact point.
            n0 = (vb0 - va0) + ((wb1 * rb2) - (wb2 * rb1)) - ((wa1 * ra2) - (wa2 * ra1));
            n1 = (vb1 - va1) + ((wb2 * rb0) - (wb0 * rb2)) - ((wa2 * ra0) - (wa0 * ra2));
            n2 = (vb2 - va2) + ((wb0 * rb1) - (wb1 * rb0)) - ((wa0 * ra1) - (wa1 * ra0));

            // Friction tangent and bitangent constraint space impulses.
            var lambdau = ((u0 * n0) + (u1 * n1) + (u2 * n2));
            var lambdav = ((v0 * n0) + (v1 * n1) + (v2 * n2));

            // Transform by inverse mass matrix.
            j1 = lambdau * data[46] + lambdav * data[47];
            var j2 = lambdau * data[47] + lambdav * data[48];

            // Accumulate and clamp.
            jOld1 = data[41];
            var jOld2 = data[42];
            cjAcc1 = jOld1 + j1;
            var cjAcc2 = jOld2 + j2;

            var jMax = friction * data[40];
            var fsq = (cjAcc1 * cjAcc1) + (cjAcc2 * cjAcc2);
            if (fsq > (jMax * jMax)) {
                fsq = jMax / Math.sqrt(fsq);
                cjAcc1 *= fsq;
                cjAcc2 *= fsq;
                j1 = cjAcc1 - jOld1;
                j2 = cjAcc2 - jOld2;
            }
            data[41] = cjAcc1;
            data[42] = cjAcc2;

            // Apply friction impulse.
            n0 = (u0 * j1) + (v0 * j2);
            n1 = (u1 * j1) + (v1 * j2);
            n2 = (u2 * j1) + (v2 * j2);

            va0 += (n0 * imA);
            va1 += (n1 * imA);
            va2 += (n2 * imA);

            vb0 -= (n0 * imB);
            vb1 -= (n1 * imB);
            vb2 -= (n2 * imB);

            wa0 += (data[28] * j1) + (data[34] * j2);
            wa1 += (data[29] * j1) + (data[35] * j2);
            wa2 += (data[30] * j1) + (data[36] * j2);

            wb0 += (data[31] * j1) + (data[37] * j2);
            wb1 += (data[32] * j1) + (data[38] * j2);
            wb2 += (data[33] * j1) + (data[39] * j2);
        }

        // Set local vars to velocities.
        vec = objectA.velocity;
        vec[0] = va0;
        vec[1] = va1;
        vec[2] = va2;
        vec[3] = wa0;
        vec[4] = wa1;
        vec[5] = wa2;

        vec = objectB.velocity;
        vec[0] = vb0;
        vec[1] = vb1;
        vec[2] = vb2;
        vec[3] = wb0;
        vec[4] = wb1;
        vec[5] = wb2;
    };

    WebGLPhysicsArbiter.prototype.invalidateParameters = function () {
        this.restitution = (this.objectA.restitution * this.objectB.restitution);
        this.friction = (this.objectA.friction * this.objectB.friction);
    };

    WebGLPhysicsArbiter.allocate = function (shapeA, shapeB, objectA, objectB) {
        var arbiter;
        if (this.arbiterPoolSize === 0) {
            arbiter = new WebGLPhysicsArbiter();
        } else {
            arbiter = this.arbiterPool[this.arbiterPoolSize - 1];
            this.arbiterPoolSize -= 1;
        }

        arbiter.active = true;

        arbiter.shapeA = shapeA;
        arbiter.shapeB = shapeB;
        arbiter.objectA = objectA;
        arbiter.objectB = objectB;
        arbiter.invalidateParameters();

        return arbiter;
    };

    WebGLPhysicsArbiter.deallocate = function (arbiter) {
        // Prevent object pooled arbiter from keeping shapes/objects
        // from potential GC.
        arbiter.shapeA = null;
        arbiter.shapeB = null;
        arbiter.objectA = null;
        arbiter.objectB = null;

        // Ensure flag is reset.
        arbiter.skipDiscreteCollisions = false;

        // clear contact information
        arbiter.activeContacts.length = 0;
        arbiter.contactFlags = 0;
        arbiter.trigger = false;

        this.arbiterPool[this.arbiterPoolSize] = arbiter;
        this.arbiterPoolSize += 1;
    };
    WebGLPhysicsArbiter.version = 1;

    WebGLPhysicsArbiter.arbiterPool = [];
    WebGLPhysicsArbiter.arbiterPoolSize = 0;
    return WebGLPhysicsArbiter;
})();

//
// WebGLPhysicsIsland
//
var WebGLPhysicsIsland = (function () {
    function WebGLPhysicsIsland() {
        // Initialise all properties of islands
        // which will ever be used.
        // Set of rigid bodies in island
        this.bodies = [];

        // Set of constraints in island
        this.constraints = [];

        // Local max wakeTimeStamp for island
        this.wakeTimeStamp = 0;

        // Active state of island (compared to sleeping)
        this.active = false;

        return this;
    }
    WebGLPhysicsIsland.allocate = function () {
        var island;
        if (this.islandPoolSize === 0) {
            island = new WebGLPhysicsIsland();
        } else {
            island = this.islandPool[this.islandPoolSize - 1];
            this.islandPoolSize -= 1;
        }

        return island;
    };

    WebGLPhysicsIsland.deallocate = function (island) {
        this.islandPool[this.islandPoolSize] = island;
        this.islandPoolSize += 1;

        // Make sure to reset local max wakeTimeStamp back to 0.
        island.wakeTimeStamp = 0;
    };
    WebGLPhysicsIsland.version = 1;

    WebGLPhysicsIsland.islandPool = [];
    WebGLPhysicsIsland.islandPoolSize = 0;
    return WebGLPhysicsIsland;
})();

//
// WebGLPhysicsTriangleShape
//
var WebGLPhysicsTriangleShape = (function () {
    function WebGLPhysicsTriangleShape() {
        // Initialise all properties of Triangle shape
        // which will ever be used.
        // Index into parent WebGLTriangleArray::triangles list.
        this.index = 0;

        // Collision radius in collision algorithms, this is taken from parent mesh shape.
        this.collisionRadius = 0;

        // The parent TriangleArray.
        this.triangleArray = null;

        return this;
    }
    WebGLPhysicsTriangleShape.prototype.localSupportWithoutMargin = function (vec, dst) {
        var vec0 = vec[0];
        var vec1 = vec[1];
        var vec2 = vec[2];

        var triangles = this.triangleArray.triangles;
        var triangle = this.index;

        var v00 = triangles[triangle + 3];
        var v01 = triangles[triangle + 4];
        var v02 = triangles[triangle + 5];
        var u0 = triangles[triangle + 6];
        var u1 = triangles[triangle + 7];
        var u2 = triangles[triangle + 8];
        var v0 = triangles[triangle + 9];
        var v1 = triangles[triangle + 10];
        var v2 = triangles[triangle + 11];

        var dotu = ((vec0 * u0) + (vec1 * u1) + (vec2 * u2));
        var dotv = ((vec0 * v0) + (vec1 * v1) + (vec2 * v2));

        if (dotu <= 0 && dotv <= 0) {
            dst[0] = v00;
            dst[1] = v01;
            dst[2] = v02;
        } else if (dotu >= dotv) {
            dst[0] = (v00 + u0);
            dst[1] = (v01 + u1);
            dst[2] = (v02 + u2);
        } else {
            dst[0] = (v00 + v0);
            dst[1] = (v01 + v1);
            dst[2] = (v02 + v2);
        }
    };

    WebGLPhysicsTriangleShape.allocate = function () {
        var triangle;
        if (this.trianglePoolSize === 0) {
            triangle = new WebGLPhysicsTriangleShape();
        } else {
            triangle = this.trianglePool[this.trianglePoolSize - 1];
            this.trianglePoolSize -= 1;
        }

        return triangle;
    };

    WebGLPhysicsTriangleShape.deallocate = function (triangle) {
        this.trianglePool[this.trianglePoolSize] = triangle;
        this.trianglePoolSize += 1;

        // Ensure reference is null'ed so that an object pooled Triangle Shape
        // cannot prevent the TriangleArray from being GC'ed.
        triangle.triangleArray = null;
    };
    WebGLPhysicsTriangleShape.version = 1;

    WebGLPhysicsTriangleShape.trianglePool = [];
    WebGLPhysicsTriangleShape.trianglePoolSize = 0;
    return WebGLPhysicsTriangleShape;
})();

WebGLPhysicsTriangleShape.prototype.type = "TRIANGLE_MESH_TRIANGLE";

//
// WebGLPhysicsTOIEvent
//
var WebGLPhysicsTOIEvent = (function () {
    function WebGLPhysicsTOIEvent() {
        // Initialise all properties of TOI Event
        // which will ever be used.
        //
        // This object is made to dual as a cache in contactPairTest.
        // Objects TOI Event relates to.
        this.objectA = null;
        this.objectB = null;

        // Shapes TOI Event relates to.
        // Precondition: object#.shape = shape#
        this.shapeA = null;
        this.shapeB = null;

        // Closest points on shapes forming the contact point.
        this.closestA = VMath.v3BuildZero();
        this.closestB = VMath.v3BuildZero();

        // Seperating axis / MTV axis forming contact normal.
        this.axis = VMath.v3BuildZero();

        // Penetration distance for contact of TOI event.
        this.distance = 0.0;

        // Time of impact for this event.
        this.toi = 0.0;

        // Cache defining the frozen state of objects during continuous collision detection.
        // Used to invalidate TOI Event when an object's sweepFrozen differs.
        this.frozenA = false;
        this.frozenB = false;

        // Marks this event as corresponding to a concave triangle mesh.
        // This value is passed to insertContact to prevent culling contacts
        // based on normals.
        this.concave = false;

        return this;
    }
    WebGLPhysicsTOIEvent.allocate = function () {
        var toi;
        if (this.eventPoolSize === 0) {
            toi = new WebGLPhysicsTOIEvent();
        } else {
            toi = this.eventPool[this.eventPoolSize - 1];
            this.eventPoolSize -= 1;
        }

        return toi;
    };

    WebGLPhysicsTOIEvent.deallocate = function (toi) {
        this.eventPool[this.eventPoolSize] = toi;
        this.eventPoolSize += 1;

        if (toi.concave) {
            WebGLPhysicsTriangleShape.deallocate(toi.shapeB);
            toi.concave = false;
        }

        // Ensure that object references are set to null to permit GC
        // even if this is in the object pool.
        toi.objectA = null;
        toi.objectB = null;
        toi.shapeA = null;
        toi.shapeB = null;
    };
    WebGLPhysicsTOIEvent.version = 1;

    WebGLPhysicsTOIEvent.eventPool = [];
    WebGLPhysicsTOIEvent.eventPoolSize = 0;
    return WebGLPhysicsTOIEvent;
})();

//
// WebGLPhysicsWorld
//
var WebGLPhysicsWorld = (function () {
    function WebGLPhysicsWorld() {
    }
    WebGLPhysicsWorld.prototype.update = function () {
        this._private.update();
    };

    WebGLPhysicsWorld.prototype.rayTest = function (ray) {
        return this._private.rayTest(ray);
    };

    WebGLPhysicsWorld.prototype.convexSweepTest = function (params) {
        return this._private.convexSweepTest(params);
    };

    WebGLPhysicsWorld.prototype.addCollisionObject = function (collisionObject) {
        return this._private.addBody((collisionObject)._private);
    };

    WebGLPhysicsWorld.prototype.removeCollisionObject = function (collisionObject) {
        return this._private.removeBody((collisionObject)._private);
    };

    WebGLPhysicsWorld.prototype.addRigidBody = function (rigidBody) {
        return this._private.addBody((rigidBody)._private);
    };

    WebGLPhysicsWorld.prototype.removeRigidBody = function (rigidBody) {
        return this._private.removeBody((rigidBody)._private);
    };

    WebGLPhysicsWorld.prototype.addConstraint = function (constraint) {
        return this._private.addConstraint((constraint)._private);
    };

    WebGLPhysicsWorld.prototype.removeConstraint = function (constraint) {
        return this._private.removeConstraint((constraint)._private);
    };

    WebGLPhysicsWorld.prototype.addCharacter = function (character) {
        return this._private.addBody((character)._private.rigidBody._private);
    };

    WebGLPhysicsWorld.prototype.removeCharacter = function (character) {
        return this._private.removeBody((character)._private.rigidBody._private);
    };

    WebGLPhysicsWorld.prototype.wakeBody = function (body) {
        this._private.wakeBody(body);
    };

    WebGLPhysicsWorld.prototype.flush = function () {
        this._private.flush();
    };

    WebGLPhysicsWorld.create = function (params) {
        var rets = new WebGLPhysicsWorld();
        var s = new WebGLPrivatePhysicsWorld();
        rets._private = s;
        s._public = rets;

        s.gravity = (params.gravity !== undefined) ? VMath.v3Copy(params.gravity) : VMath.v3Build(0, -10, 0);
        s.maxSubSteps = (params.maxSubSteps !== undefined) ? params.maxSubSteps : 10;

        s.fixedTimeStep = (params.fixedTimeStep !== undefined) ? params.fixedTimeStep : (1 / 60);

        s.variableMinStep = (params.minimumTimeStep !== undefined) ? params.minimumTimeStep : (1 / 70);
        s.variableMaxStep = (params.maximumTimeStep !== undefined) ? params.maximumTimeStep : (1 / 50);

        s.variableStep = (params.variableTimeSteps !== undefined) ? params.variableTimeSteps : false;

        s.maxGiveUpTimeStep = (params.maxGiveUpTimeStep !== undefined) ? params.maxGiveUpTimeStep : 1 / 20;

        // read only properties
        Object.defineProperty(rets, "maxSubSteps", {
            value: s.maxSubSteps,
            enumerable: true
        });

        Object.defineProperty(rets, "maxGiveUpTimeStep", {
            value: s.maxGiveUpTimeStep,
            enumerable: true
        });

        if (!s.variableStep) {
            Object.defineProperty(rets, "fixedTimeStep", {
                value: s.fixedTimeStep,
                enumerable: true
            });
        } else {
            Object.defineProperty(rets, "minimumTimeStep", {
                value: s.variableMinStep,
                enumerable: true
            });
            Object.defineProperty(rets, "maximumTimeStep", {
                value: s.variableMaxStep,
                enumerable: true
            });
        }

        // read only, getter needed to make copy
        Object.defineProperty(rets, "gravity", {
            get: function physicsWorldGetGravity() {
                return VMath.v3Copy(this._private.gravity);
            },
            enumerable: true
        });

        s.staticSpatialMap = AABBTree.create(true);
        s.dynamicSpatialMap = AABBTree.create();
        s.sleepingSpatialMap = AABBTree.create();

        s.collisionObjects = [];
        s.rigidBodies = [];
        s.constraints = [];
        s.kinematicBodies = [];

        // List of active arbiters between shapes.
        s.activeArbiters = [];

        // List of active rigid bodies and constraints.
        s.activeBodies = [];
        s.activeKinematics = [];
        s.activeConstraints = [];

        s.persistantObjectsList = [];
        s.persistantObjectsList2 = [];
        s.persistantTrianglesList = [];
        s.persistantTOIEventList = [];

        s.timeStamp = 0;

        // timing information
        s.performanceData = {
            discrete: 0,
            sleepComputation: 0,
            prestepContacts: 0,
            prestepConstraints: 0,
            integrateVelocities: 0,
            warmstartContacts: 0,
            warmstartConstraints: 0,
            physicsIterations: 0,
            integratePositions: 0,
            continuous: 0
        };

        // read only, no getter needed
        Object.defineProperty(rets, "performanceData", {
            value: s.performanceData,
            enumerable: true
        });

        // Extents used throughout all calls to syncBody
        s.syncExtents = new Float32Array(6);

        // Array for all the objects we need to call for contact callbacks
        s.contactCallbackObjects = [];

        // Array for all the removed arbiters
        s.contactCallbackRemovedArbiters = [];

        return rets;
    };
    WebGLPhysicsWorld.version = 1;
    return WebGLPhysicsWorld;
})();

var WebGLPrivatePhysicsWorld = (function () {
    function WebGLPrivatePhysicsWorld() {
    }
    WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformVector = function (m, v, dst) {
        if (dst === undefined) {
            dst = new Float32Array(3);
        }
        var v0 = v[0];
        var v1 = v[1];
        var v2 = v[2];
        dst[0] = (m[0] * v0 + m[1] * v1 + m[2] * v2);
        dst[1] = (m[3] * v0 + m[4] * v1 + m[5] * v2);
        dst[2] = (m[6] * v0 + m[7] * v1 + m[8] * v2);
        return dst;
    };

    WebGLPrivatePhysicsWorld.prototype.m43InverseOrthonormalTransformPoint = function (m, v, dst) {
        if (dst === undefined) {
            dst = new Float32Array(3);
        }
        var v0 = v[0] - m[9];
        var v1 = v[1] - m[10];
        var v2 = v[2] - m[11];
        dst[0] = (m[0] * v0 + m[1] * v1 + m[2] * v2);
        dst[1] = (m[3] * v0 + m[4] * v1 + m[5] * v2);
        dst[2] = (m[6] * v0 + m[7] * v1 + m[8] * v2);
        return dst;
    };

    // Determine if shape intersects the plane containing triangle
    // number 'index' in triangle array With given shape and triangle
    // transforms.
    WebGLPrivatePhysicsWorld.prototype.trianglePlaneDiscard = function (shape, xform, triangleArray, index, txform) {
        if (this.planeAxis === undefined) {
            this.planeAxis = VMath.v3BuildZero();
            this.planeSA = VMath.v3BuildZero();
            this.planeSB = VMath.v3BuildZero();
        }
        var axis = this.planeAxis;
        var supportA = this.planeSA;
        var supportB = this.planeSB;

        var triangles = triangleArray.triangles;

        // local plane normal and distance.
        var n0 = triangles[index];
        var n1 = triangles[index + 1];
        var n2 = triangles[index + 2];
        var nd = triangles[index + 16];

        var A0 = txform[0];
        var A1 = txform[1];
        var A2 = txform[2];
        var A3 = txform[3];
        var A4 = txform[4];
        var A5 = txform[5];
        var A6 = txform[6];
        var A7 = txform[7];
        var A8 = txform[8];
        var A9 = txform[9];
        var A10 = txform[10];
        var A11 = txform[11];

        // transform plane normal into world space.
        var w0 = (n0 * A0) + (n1 * A3) + (n2 * A6);
        var w1 = (n0 * A1) + (n1 * A4) + (n2 * A7);
        var w2 = (n0 * A2) + (n1 * A5) + (n2 * A8);

        A0 = xform[0];
        A1 = xform[1];
        A2 = xform[2];
        A3 = xform[3];
        A4 = xform[4];
        A5 = xform[5];
        A6 = xform[6];
        A7 = xform[7];
        A8 = xform[8];
        A9 -= xform[9];
        A10 -= xform[10];
        A11 -= xform[11];

        // transform plane into shape local space.
        n0 = (A0 * w0) + (A1 * w1) + (A2 * w2);
        n1 = (A3 * w0) + (A4 * w1) + (A5 * w2);
        n2 = (A6 * w0) + (A7 * w1) + (A8 * w2);
        nd += (w0 * A9) + (w1 * A10) + (w2 * A11);

        // find maximum and minimal support points on shape.
        axis[0] = n0;
        axis[1] = n1;
        axis[2] = n2;
        shape.localSupportWithoutMargin(axis, supportA);

        axis[0] = -n0;
        axis[1] = -n1;
        axis[2] = -n2;
        shape.localSupportWithoutMargin(axis, supportB);

        // Find distance from plane for each support.
        var dot1 = (supportA[0] * n0) + (supportA[1] * n1) + (supportA[2] * n2) - nd;
        var dot2 = (supportB[0] * n0) + (supportB[1] * n1) + (supportB[2] * n2) - nd;

        if ((dot1 * dot2) <= 0) {
            return false;
        }

        // Choose closest support to plane for distance computation
        // with margins.
        var seperation;
        if ((dot1 * dot1) < (dot2 * dot2)) {
            seperation = dot1;
        } else {
            seperation = dot2;
        }

        if ((seperation < 0) !== ((dot1 * dot2) < 0)) {
            seperation = -seperation;
        }

        return (seperation - shape.collisionRadius) > 0;
    };

    // Determine if pair of objects is permitted to collide.
    WebGLPrivatePhysicsWorld.prototype.filtered = function (objectA, objectB) {
        if (objectA === objectB) {
            return true;
        }

        if ((objectA.collisionObject || objectA.kinematic) && (objectB.collisionObject || objectB.kinematic)) {
            return true;
        }

        if ((objectA.mask & objectB.group) === 0 || (objectB.mask & objectA.group) === 0) {
            return true;
        }

        /*jshint bitwise: true*/
        return false;
    };

    // perform narrow phase collision detection between shapes A and B
    // owned by respective objects objectA, objectB (objectA.shape ===
    // shapeA etc)
    WebGLPrivatePhysicsWorld.prototype.narrowPhase = function (shapeA, shapeB, objectA, objectB) {
        if (this.narrowTriangle === undefined) {
            // Fake triangle shape for TRIANGLE_MESH collisions.
            this.narrowTriangle = WebGLPhysicsTriangleShape.allocate();

            // contactPairTest cache object.
            this.narrowCache = {
                axis: VMath.v3Build(1, 0, 0),
                shapeA: null,
                shapeB: null,
                closestA: VMath.v3BuildZero(),
                closestB: VMath.v3BuildZero()
            };

            // contactPairTest cache object used in TRIANGLE_MESH
            // as shapeA/shapeB are not the same as above.
            this.narrowCache2 = {
                axis: this.narrowCache.axis,
                shapeA: null,
                shapeB: null,
                closestA: this.narrowCache.closestA,
                closestB: this.narrowCache.closestB
            };

            // Fake body used for TRIANGLE_MESH to compute local extents of
            // A shape in triangle mesh local-coordinates.
            this.narrowFakeBody = {
                transform: VMath.m43BuildIdentity(),
                shape: null
            };

            this.narrowTransform = VMath.m43BuildIdentity();
            this.narrowExtents = new Float32Array(6);
        }

        // Find existing arbiter for shape pair.
        // Iterating the smaller list of either object.
        var arb = null;
        var arbitersA = objectA.arbiters;
        var arbitersB = objectB.arbiters;
        var arbiters = (arbitersA.length <= arbitersB.length) ? arbitersA : arbitersB;

        var i = 0;
        var numArbiters = arbiters.length;
        for (i = 0; i < numArbiters; i += 1) {
            var carb = arbiters[i];
            if (carb.shapeA === shapeA && carb.shapeB === shapeB && carb.objectA === objectA && carb.objectB === objectB) {
                arb = carb;
                break;
            }
        }

        if (arb !== null && arb.skipDiscreteCollisions) {
            arb.skipDiscreteCollisions = false;
            return;
        }

        // If arbiter does not already exist, create a new one.
        var fresh = (arb === null);
        if (fresh) {
            arb = WebGLPhysicsArbiter.allocate(shapeA, shapeB, objectA, objectB);
        }

        var cache = this.narrowCache;
        cache.shapeA = shapeA;
        cache.shapeB = shapeB;

        if (arb.contacts.length !== 0) {
            //var basis = arb.contacts[0].basis;
            var data = arb.contacts[0];

            // VMath.v3Copy(c.normal, cache.axis);
            cache.axis[0] = data[12];
            cache.axis[1] = data[13];
            cache.axis[2] = data[14];
        }

        var contact;
        var collided = false;

        if (shapeA.type === "TRIANGLE_MESH" || shapeB.type === "TRIANGLE_MESH") {
            var meshShape, otherShape;
            var meshXForm, otherXForm;
            var triangle = this.narrowTriangle;
            var cache2 = this.narrowCache2;

            if (shapeA.type === "TRIANGLE_MESH") {
                meshShape = shapeA;
                meshXForm = objectA.transform;
                otherShape = shapeB;
                otherXForm = objectB.transform;
                cache2.shapeA = triangle;
                cache2.shapeB = cache.shapeB;
            } else {
                meshShape = shapeB;
                meshXForm = objectB.transform;
                otherShape = shapeA;
                otherXForm = objectA.transform;
                cache2.shapeA = cache.shapeA;
                cache2.shapeB = triangle;
            }

            var triangleArray = meshShape.triangleArray;
            triangle.triangleArray = triangleArray;
            triangle.collisionRadius = meshShape.collisionRadius;

            var numTriangles;

            if (triangleArray.spatialMap) {
                // determine AABB of non-triangle mesh object in local coordinates
                // of triangle mesh.
                var transform = this.narrowTransform;
                var fakeBody = this.narrowFakeBody;
                var extents = this.narrowExtents;

                //var itransform = VMath.m43InverseOrthonormal(meshXForm);
                VMath.m43InverseOrthonormal(meshXForm, transform);
                VMath.m43Mul(otherXForm, transform, fakeBody.transform);
                fakeBody.shape = otherShape;
                WebGLPhysicsPrivateBody.prototype.calculateExtents.call(fakeBody, extents);

                // Find all triangles to test against.
                var triangles = this.persistantTrianglesList;
                numTriangles = triangleArray.spatialMap.getOverlappingNodes(extents, triangles, 0);
                for (i = 0; i < numTriangles; i += 1) {
                    var index = triangles[i].index;
                    triangle.index = index;

                    // Prevent GC issues from object being kept in persistent array
                    triangles[i] = undefined;

                    if (!this.trianglePlaneDiscard(otherShape, otherXForm, triangleArray, index, meshXForm)) {
                        contact = this.contactPairTest(cache2, objectA.transform, objectB.transform);
                        if (contact < 0) {
                            arb.insertContact(cache2.closestA, cache2.closestB, cache2.axis, contact, true);
                            collided = true;
                        }
                    }
                }
            } else {
                // If triangle mesh is small, no AABBTree exists
                // And we check all triangles brute-force.
                numTriangles = triangleArray.numTriangles;
                for (i = 0; i < numTriangles; i += 1) {
                    triangle.index = (i * WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE);
                    if (!this.trianglePlaneDiscard(otherShape, otherXForm, triangleArray, triangle.index, meshXForm)) {
                        contact = this.contactPairTest(cache2, objectA.transform, objectB.transform);
                        if (contact < 0) {
                            arb.insertContact(cache2.closestA, cache2.closestB, cache2.axis, contact, true);
                            collided = true;
                        }
                    }
                }
            }
        } else {
            contact = this.contactPairTest(cache, objectA.transform, objectB.transform);
            if (contact < 0) {
                arb.insertContact(cache.closestA, cache.closestB, cache.axis, contact, false);
                collided = true;
            }
        }

        if (collided) {
            if (fresh) {
                this.activeArbiters.push(arb);
                arb.active = true;
                objectA.arbiters.push(arb);
                objectB.arbiters.push(arb);
            }

            if (objectA.permitSleep && !objectA.active) {
                this.wakeBody(objectA);
            }
            if (objectB.permitSleep && !objectB.active) {
                this.wakeBody(objectB);
            }

            if (!arb.active) {
                arb.active = true;
                this.activeArbiters.push(arb);
            }
        } else if (fresh) {
            // New arbiter, but no collision means we should
            // immediately deallocate for re-use.
            WebGLPhysicsArbiter.deallocate(arb);
        }
    };

    // Compute islands of interaction rigid bodies and constraints
    // And put to sleep those islands that are to be considered
    // stationary.
    WebGLPrivatePhysicsWorld.prototype.computeSleeping = function (timeStep) {
        // Implementation of union-find algorithm with union by rank
        // and path compression.
        function _unify(x, y) {
            var xr = _find(x);
            var yr = _find(y);
            if (xr !== yr) {
                if (xr.islandRank < yr.islandRank) {
                    xr.islandRoot = yr;
                } else if (xr.islandRank > yr.islandRank) {
                    yr.islandRoot = xr;
                } else {
                    yr.islandRoot = xr;
                    xr.islandRank += 1;
                }
            }
        }

        function _find(x) {
            if (x === x.islandRoot) {
                return x;
            }

            var root = x;
            var stack = null;
            var next;
            while (root !== root.islandRoot) {
                next = root.islandRoot;
                root.islandRoot = stack;
                stack = root;
                root = next;
            }

            while (stack !== null) {
                next = stack.islandRoot;
                stack.islandRoot = root;
                stack = next;
            }
            return root;
        }

        var objectA, objectB;

        // Build disjoint set forest
        // based on active arbiters and constraints.
        var arbiters = this.activeArbiters;
        var bodies = this.activeBodies;
        var constraints = this.activeConstraints;

        var n;
        var maxN = arbiters.length;
        for (n = 0; n < maxN; n += 1) {
            var arb = arbiters[n];
            objectA = arb.objectA;
            objectB = arb.objectB;
            if (objectA.permitSleep && objectB.permitSleep) {
                _unify(objectA, objectB);
            }
        }

        maxN = constraints.length;
        var con;
        for (n = 0; n < maxN; n += 1) {
            con = constraints[n];
            objectA = con.bodyA;
            objectB = con.bodyB;
            if (objectA && objectA.permitSleep) {
                _unify(objectA, con);
            }
            if (objectB && objectB.permitSleep) {
                _unify(objectB, con);
            }
        }

        // Build islands
        var islands = [];
        var island, body, root;
        while (bodies.length > 0) {
            body = bodies.pop();
            root = _find(body);
            island = root.island;
            if (!island) {
                island = root.island = WebGLPhysicsIsland.allocate();
                islands.push(island);
                island.active = false;
            }

            body.island = island;
            island.bodies.push(body);
            island.active = island.active || body.isActive(timeStep);
            if (body.wakeTimeStamp > island.wakeTimeStamp) {
                island.wakeTimeStamp = body.wakeTimeStamp;
            }
        }

        while (constraints.length > 0) {
            con = constraints.pop();
            root = _find(con);
            island = root.island;
            if (!island) {
                island = root.island = WebGLPhysicsIsland.allocate();
                islands.push(island);
                island.active = true;
            }

            con.island = island;
            island.constraints.push(con);
            if (con.wakeTimeStamp > island.wakeTimeStamp) {
                island.wakeTimeStamp = con.wakeTimeStamp;
            }
        }

        while (islands.length > 0) {
            island = islands.pop();
            if (island.active) {
                while (island.bodies.length > 0) {
                    body = island.bodies.pop();
                    body.wakeTimeStamp = island.wakeTimeStamp;
                    bodies.push(body);

                    // reset for next iteration of computeSleeping
                    body.islandRoot = body;
                    body.islandRank = 0;
                    body.island = null;
                }

                while (island.constraints.length > 0) {
                    con = island.constraints.pop();
                    con.wakeTimeStamp = island.wakeTimeStamp;
                    constraints.push(con);

                    // reset for next iteration of computeSleeping
                    con.islandRoot = con;
                    con.islandRank = 0;
                    con.island = null;
                }

                WebGLPhysicsIsland.deallocate(island);
            } else {
                maxN = island.bodies.length;
                for (n = 0; n < maxN; n += 1) {
                    body = island.bodies[n];
                    body.velocity[0] = body.velocity[1] = body.velocity[2] = 0;
                    body.velocity[3] = body.velocity[4] = body.velocity[5] = 0;
                    body.active = false;
                    this.syncBody(body);

                    // reset for next iteration of computeSleeping
                    body.islandRoot = body;
                    body.islandRank = 0;
                }

                maxN = island.constraints.length;
                for (n = 0; n < maxN; n += 1) {
                    con = island.constraints[n];
                    con.active = false;

                    // reset for next iteration of computeSleeping
                    con.islandRoot = con;
                    con.islandRank = 0;
                }
            }
        }
    };

    // Wake up a sleeping island.
    WebGLPrivatePhysicsWorld.prototype.wakeIsland = function (island) {
        while (island.bodies.length > 0) {
            var body = island.bodies.pop();
            body.wakeTimeStamp = this.timeStamp + (this.midStep ? 0 : 1);
            this.activeBodies.push(body);

            var n;
            var arbiters = body.arbiters;
            var maxN = arbiters.length;
            for (n = 0; n < maxN; n += 1) {
                var arb = arbiters[n];
                if (!arb.active) {
                    arb.active = true;
                    this.activeArbiters.push(arb);
                }
            }

            body.active = true;
            body.island = null;
            this.syncBody(body);
        }

        while (island.constraints.length > 0) {
            var constraint = island.constraints.pop();
            constraint.wakeTimeStamp = this.timeStamp + (this.midStep ? 0 : 1);
            this.activeConstraints.push(constraint);

            constraint.active = true;
            constraint.island = null;
        }

        WebGLPhysicsIsland.deallocate(island);
    };

    WebGLPrivatePhysicsWorld.prototype.wakeRelated = function (body) {
        // Wake any related constraints
        var constraints = body.constraints;
        var n;
        var maxN = constraints.length;
        for (n = 0; n < maxN; n += 1) {
            this.wakeConstraint(constraints[n]);
        }

        // Wake any touching bodies
        var arbiters = body.arbiters;
        maxN = arbiters.length;
        for (n = 0; n < maxN; n += 1) {
            var arb = arbiters[n];
            if (!arb.active) {
                arb.active = true;
                this.activeArbiters.push(arb);
            }

            if (arb.objectA.permitSleep && !arb.objectA.active) {
                this.wakeBody(arb.objectA);
            }
            if (arb.objectB.permitSleep && !arb.objectB.active) {
                this.wakeBody(arb.objectB);
            }
        }
    };

    // TODO: Should this be taking the private object?  Seems to be
    // given a WebGLPhysicsPrivateBody, but puts it on
    // activeKinematics which
    // Wake up a rigid body.
    WebGLPrivatePhysicsWorld.prototype.wakeBody = function (body) {
        if (body.collisionObject && !body.kinematic) {
            this.wakeRelated(body);
            this.syncBody(body);
        } else if (body.kinematic) {
            body.delaySleep = true;
            if (!body.active) {
                body.active = true;
                this.activeKinematics.push(body);

                this.wakeRelated(body);
                this.syncBody(body);
            }
        } else {
            body.wakeTimeStamp = this.timeStamp + (this.midStep ? 0 : 1);
            if (!body.active) {
                if (!body.island) {
                    body.active = true;
                    this.activeBodies.push(body);

                    this.wakeRelated(body);
                    this.syncBody(body);
                } else {
                    this.wakeIsland(body.island);
                }

                // Synchronise body with broadphase.
                this.syncBody(body);
            }
        }
    };

    // Sync body with broadphase
    WebGLPrivatePhysicsWorld.prototype.syncBody = function (body) {
        var extents = this.syncExtents;
        body.calculateExtents(extents);
        if (body.collisionObject && !body.kinematic) {
            this.staticSpatialMap.update(body, extents);
        } else {
            if (body.active) {
                if (!body.previouslyActive) {
                    this.sleepingSpatialMap.remove(body);
                    this.dynamicSpatialMap.add(body, extents);
                } else {
                    this.dynamicSpatialMap.update(body, extents);
                }
            } else {
                if (body.previouslyActive) {
                    this.dynamicSpatialMap.remove(body);
                    this.sleepingSpatialMap.add(body, extents);
                } else {
                    this.sleepingSpatialMap.update(body, extents);
                }
            }

            body.previouslyActive = body.active;
        }
    };

    // Wake up a constraint
    WebGLPrivatePhysicsWorld.prototype.wakeConstraint = function (constraint) {
        constraint.wakeTimeStamp = this.timeStamp + (this.midStep ? 0 : 1);
        if (!constraint.active) {
            if (!constraint.island) {
                constraint.active = true;
                this.activeConstraints.push(constraint);

                if (constraint.bodyA) {
                    this.wakeBody(constraint.bodyA);
                }
                if (constraint.bodyB) {
                    this.wakeBody(constraint.bodyB);
                }
            } else {
                this.wakeIsland(constraint.island);
            }
        }
    };

    // Implemenmtation of Conservative Advancement for two moving objects.
    WebGLPrivatePhysicsWorld.prototype.dynamicSweep = function (toi, timeStep, lowerBound, negRadius) {
        var objectA = toi.objectA;
        var objectB = toi.objectB;
        var axis = toi.axis;

        // Compute start guess on best axis.
        var vel1 = objectA.velocity;
        var axis0 = -vel1[0];
        var axis1 = -vel1[1];
        var axis2 = -vel1[2];

        var vel2 = objectB.velocity;
        axis0 += vel2[0];
        axis1 += vel2[1];
        axis2 += vel2[2];

        if (((axis0 * axis0) + (axis1 * axis1) + (axis2 * axis2)) < WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            toi.toi = undefined;
            return;
        }

        axis[0] = axis0;
        axis[1] = axis1;
        axis[2] = axis2;

        // Compute relative linear velocity, and angular bias for distance calculations.
        var delta0 = -axis0;
        var delta1 = -axis1;
        var delta2 = -axis2;
        var angBias = 0;

        var radiusA, radiusB;
        if (!objectA.fixedRotation) {
            radiusA = objectA.shape.radius;
            angBias += radiusA * Math.sqrt((vel1[3] * vel1[3]) + (vel1[4] * vel1[4]) + (vel1[5] * vel1[5]));
        }

        if (!objectB.fixedRotation) {
            radiusB = objectB.shape.radius;
            angBias += radiusB * Math.sqrt((vel2[3] * vel2[3]) + (vel2[4] * vel2[4]) + (vel2[5] * vel2[5]));
        }

        if (angBias < (WebGLPhysicsConfig.CONTINUOUS_ANGULAR_BULLET / timeStep)) {
            var radius = (radiusA < radiusB) ? radiusA : radiusB;
            radius *= WebGLPhysicsConfig.CONTINUOUS_LINEAR_BULLET / timeStep;
            if (((delta0 * delta0) + (delta1 * delta1) + (delta2 * delta2)) < (radius * radius)) {
                toi.toi = undefined;
                return;
            }
        }

        var curIter = 0;
        var maxIter = 100;
        var curTOI = lowerBound;
        for (; ;) {
            objectA.integratePosition(curTOI * timeStep);
            objectB.integratePosition(curTOI * timeStep);

            var nextContact = this.contactPairTest(toi, objectA.transform, objectB.transform);
            var seperation = nextContact;
            if (nextContact !== undefined) {
                seperation += negRadius;
            }

            if (seperation === undefined || seperation < WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                if (!this.seperatingTOI(toi)) {
                    toi.distance = nextContact;
                } else {
                    curTOI = undefined;
                }
                break;
            }

            // lower bound on TOI advancement.
            var dot = (axis[0] * delta0) + (axis[1] * delta1) + (axis[2] * delta2);
            var denom = (angBias - dot) * timeStep;
            if (denom <= 0) {
                curTOI = undefined;
                break;
            }

            curTOI += seperation / denom;
            if (curTOI >= 1) {
                curTOI = undefined;
                break;
            }

            curIter += 1;
            if (curIter > maxIter) {
                curTOI = undefined;
                break;
            }
        }

        toi.toi = curTOI;
    };

    // Determine if TOI event corresponds to a seperation of the
    // objects, and can be ignored.
    WebGLPrivatePhysicsWorld.prototype.seperatingTOI = function (toi) {
        var objectA = toi.objectA;
        var objectB = toi.objectB;
        var supportA = toi.closestA;
        var supportB = toi.closestB;

        var velA = objectA.velocity;
        var velB = objectB.velocity;

        var vrel0 = velA[0] - velB[0];
        var vrel1 = velA[1] - velB[1];
        var vrel2 = velA[2] - velB[2];

        if (!objectA.fixedRotation) {
            var relA0 = supportA[0] - objectA.transform[9];
            var relA1 = supportA[1] - objectA.transform[10];
            var relA2 = supportA[2] - objectA.transform[11];

            vrel0 += (velA[4] * relA2) - (velA[5] * relA1);
            vrel1 += (velA[5] * relA0) - (velA[3] * relA2);
            vrel2 += (velA[3] * relA1) - (velA[4] * relA0);
        }

        if (!objectB.fixedRotation) {
            var relB0 = supportB[0] - objectB.transform[9];
            var relB1 = supportB[1] - objectB.transform[10];
            var relB2 = supportB[2] - objectB.transform[11];

            vrel0 -= (velB[4] * relB2) - (velB[5] * relB1);
            vrel1 -= (velB[5] * relB0) - (velB[3] * relB2);
            vrel2 -= (velB[3] * relB1) - (velB[4] * relB0);
        }

        var axis = toi.axis;
        var dot = (vrel0 * axis[0]) + (vrel1 * axis[1]) + (vrel2 * axis[2]);
        return dot >= 0;
    };

    // Implemenmtation of Conservative Advancement for a moving body
    // against a static body.  (Optimised compared with dynamicSweep)
    WebGLPrivatePhysicsWorld.prototype.staticSweep = function (toi, timeStep, lowerBound, negRadius) {
        var objectA = toi.objectA;
        var objectB = toi.objectB;
        var axis = toi.axis;

        // Compute start guess on best axis.
        var vel = objectA.velocity;
        var axis0 = -vel[0];
        var axis1 = -vel[1];
        var axis2 = -vel[2];

        if (((axis0 * axis0) + (axis1 * axis1) + (axis2 * axis2)) < WebGLPhysicsConfig.DONT_NORMALIZE_THRESHOLD) {
            toi.toi = undefined;
            return;
        }

        axis[0] = axis0;
        axis[1] = axis1;
        axis[2] = axis2;

        // Compute relative linear velocity, and angular bias for distance calculations.
        var delta0 = -axis0;
        var delta1 = -axis1;
        var delta2 = -axis2;
        var angBias = 0;
        if (!objectA.fixedRotationtype) {
            angBias += objectA.shape.radius * Math.sqrt((vel[3] * vel[3]) + (vel[4] * vel[4]) + (vel[5] * vel[5]));
        }

        var curIter = 0;
        var maxIter = 100;
        var curTOI = lowerBound;
        for (; ;) {
            objectA.integratePosition(curTOI * timeStep);

            var nextContact = this.contactPairTest(toi, objectA.transform, objectB.transform);
            var seperation = nextContact;
            if (nextContact !== undefined) {
                seperation += negRadius;
            }

            if (seperation === undefined || seperation < WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                if (!this.seperatingTOI(toi)) {
                    toi.distance = nextContact;
                } else {
                    curTOI = undefined;
                }
                break;
            }

            // lower bound on TOI advancement.
            var dot = (axis[0] * delta0) + (axis[1] * delta1) + (axis[2] * delta2);
            var denom = (angBias - dot) * timeStep;
            if (denom <= 0) {
                curTOI = undefined;
                break;
            }

            curTOI += seperation / denom;
            if (curTOI >= 1) {
                curTOI = undefined;
                break;
            }

            curIter += 1;
            if (curIter > maxIter) {
                curTOI = undefined;
                break;
            }
        }

        toi.toi = curTOI;
    };

    WebGLPrivatePhysicsWorld.prototype.performStaticTOIBase = function (slop, timeStep, events, numEvents, objectA, objectB) {
        var triangles = this.persistantTrianglesList;

        if (this.continuousFakeBody === undefined) {
            this.continuousFakeBody = {
                shape: null,
                transform: VMath.m43BuildIdentity(),
                startTransform: VMath.m43BuildIdentity()
            };
            this.continuousInvTransform = VMath.m43BuildIdentity();
            this.continuousExtents = new Float32Array(6);
        }
        var fakeBody = this.continuousFakeBody;
        var invTransform = this.continuousInvTransform;
        var extents = this.continuousExtents;

        var toi;

        if (objectB.shape.type === "TRIANGLE_MESH") {
            var triangleArray = objectB.shape.triangleArray;
            var numTriangles, k;
            if (triangleArray.spatialMap) {
                fakeBody.shape = objectA.shape;

                // Find AABB encompassing swept shape, in local coordinate system of triangle mesh
                VMath.m43InverseOrthonormal(objectB.transform, invTransform);
                VMath.m43Mul(objectA.startTransform, invTransform, fakeBody.startTransform);
                VMath.m43Mul(objectA.endTransform, invTransform, fakeBody.transform);
                WebGLPhysicsPrivateBody.prototype.calculateSweptExtents.call(fakeBody, extents);

                numTriangles = triangleArray.spatialMap.getOverlappingNodes(extents, triangles, 0);
                for (k = 0; k < numTriangles; k += 1) {
                    toi = WebGLPhysicsTOIEvent.allocate();
                    toi.objectA = objectA;
                    toi.objectB = objectB;
                    toi.shapeA = objectA.shape;
                    toi.shapeB = WebGLPhysicsTriangleShape.allocate();
                    toi.shapeB.index = triangles[k].index;

                    // prevent possible GC issues
                    triangles[k] = undefined;

                    toi.shapeB.triangleArray = objectB.shape.triangleArray;
                    toi.shapeB.collisionRadius = objectB.shape.collisionRadius;
                    toi.concave = true;

                    this.staticSweep(toi, timeStep, 0, slop);
                    if (toi.toi === undefined) {
                        WebGLPhysicsTOIEvent.deallocate(toi);
                        continue;
                    }

                    toi.frozenA = false;
                    toi.frozenB = true;

                    events[numEvents] = toi;
                    numEvents += 1;
                }
            } else {
                numTriangles = triangleArray.numTriangles;
                for (k = 0; k < numTriangles; k += 1) {
                    toi = WebGLPhysicsTOIEvent.allocate();
                    toi.objectA = objectA;
                    toi.objectB = objectB;
                    toi.shapeA = objectA.shape;
                    toi.shapeB = WebGLPhysicsTriangleShape.allocate();
                    toi.shapeB.index = k * WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE;
                    toi.shapeB.triangleArray = objectB.shape.triangleArray;
                    toi.shapeB.collisionRadius = objectB.shape.collisionRadius;
                    toi.concave = true;

                    this.staticSweep(toi, timeStep, 0, slop);
                    if (toi.toi === undefined) {
                        WebGLPhysicsTOIEvent.deallocate(toi);
                        continue;
                    }

                    toi.frozenA = false;
                    toi.frozenB = true;

                    events[numEvents] = toi;
                    numEvents += 1;
                }
            }
        } else {
            toi = WebGLPhysicsTOIEvent.allocate();
            toi.objectA = objectA;
            toi.objectB = objectB;
            toi.shapeA = objectA.shape;
            toi.shapeB = objectB.shape;

            this.staticSweep(toi, timeStep, 0, slop);
            if (toi.toi === undefined) {
                WebGLPhysicsTOIEvent.deallocate(toi);
                return numEvents;
            }

            toi.frozenA = false;
            toi.frozenB = true;

            events[numEvents] = toi;
            numEvents += 1;
        }

        return numEvents;
    };

    WebGLPrivatePhysicsWorld.prototype.update = function () {
        var dynamicMap = this.dynamicSpatialMap;
        var staticMap = this.staticSpatialMap;
        var sleepingMap = this.sleepingSpatialMap;
        var rigidBodies = this.activeBodies;
        var kinematics = this.activeKinematics;
        var constraints = this.activeConstraints;
        var arbiters = this.activeArbiters;
        var gravity = this.gravity;

        var performance = this.performanceData;
        performance.discrete = 0;
        performance.sleepComputation = 0;
        performance.prestepContacts = 0;
        performance.prestepConstraints = 0;
        performance.integrateVelocities = 0;
        performance.warmstartContacts = 0;
        performance.warmstartConstraints = 0;
        performance.physicsIterations = 0;
        performance.integratePositions = 0;
        performance.continuous = 0;

        var prevTime = this.prevTimeStamp;
        if (prevTime === undefined) {
            this.prevTimeStamp = TurbulenzEngine.getTime() * 0.001;
            return;
        }

        // Compute number of sub-steps needed.
        var curTime = TurbulenzEngine.getTime() * 0.001;
        var timeDelta = (curTime - prevTime);

        var numSteps, timeStep;
        if (this.variableStep) {
            var minTimeStep = this.variableMinStep;
            var maxTimeStep = this.variableMaxStep;

            numSteps = Math.ceil(timeDelta / maxTimeStep);
            timeStep = (timeDelta / numSteps);

            if (timeStep < minTimeStep) {
                timeStep = minTimeStep;
                numSteps = Math.floor(timeDelta / timeStep);
            }

            if (numSteps > this.maxSubSteps && this.maxGiveUpTimeStep !== 0) {
                numSteps = Math.ceil(timeDelta / this.maxGiveUpTimeStep);
                timeStep = (timeDelta / numSteps);
            }
        } else {
            timeStep = this.fixedTimeStep;
            numSteps = Math.floor(timeDelta / timeStep);

            if (numSteps > this.maxSubSteps && this.maxGiveUpTimeStep !== 0) {
                numSteps = Math.ceil(timeDelta / this.maxGiveUpTimeStep);
                timeStep = (timeDelta / numSteps);
            }
        }

        if (numSteps <= 0) {
            return;
        }

        // update physics time stamp regardless of
        // capping of sub step count. Otherwise time will just accumulate endlessly.
        this.prevTimeStamp += (timeStep * numSteps);

        if (numSteps > this.maxSubSteps) {
            numSteps = this.maxSubSteps;
        }

        this.midStep = true;

        // Determine velocities for kinematic objects.
        // And move them back to their old position (Use velocity to move it forwards in sub steps)
        var limit, i;
        var body;
        limit = kinematics.length;
        for (i = 0; i < limit;) {
            body = kinematics[i];
            if (!body.computeDeltaVelocity(timeStep * numSteps, body.prevTransform, body.transform) && !body.delaySleep) {
                body.active = false;

                limit -= 1;
                kinematics[i] = kinematics[limit];
                kinematics.pop();

                this.syncBody(body);
            } else {
                VMath.m43Copy(body.transform, body.newTransform);
                VMath.m43Copy(body.prevTransform, body.transform);
                i += 1;
            }

            body.delaySleep = false;
        }

        // Perform substeps.
        var substep;
        for (substep = 0; substep < numSteps; substep += 1) {
            var j, extents;

            this.timeStamp += 1;
            var preTime;

            if (this.prevTimeStep === undefined) {
                this.prevTimeStep = timeStep;
            }

            var timeStepRatio = timeStep / this.prevTimeStep;
            this.prevTimeStep = timeStep;

            // ####################################################################
            // Update spatial maps with body positions and refresh inertia tensors.
            limit = rigidBodies.length;
            for (i = 0; i < limit; i += 1) {
                body = rigidBodies[i];

                extents = body.extents;
                body.calculateExtents(extents);
                dynamicMap.update(body, extents);

                body.refreshInertiaTensor();
            }

            limit = kinematics.length;
            for (i = 0; i < limit; i += 1) {
                body = kinematics[i];

                extents = body.extents;
                body.calculateExtents(extents);
                dynamicMap.update(body, extents);
            }

            // ####################################################################
            preTime = TurbulenzEngine.getTime() * 0.001;

            // Prepare broadphase
            staticMap.finalize();
            dynamicMap.finalize();
            sleepingMap.finalize();

            // Perform broadphase
            // We compute first pairs of dynamic-dynamic objects
            //    objects = [ a0, a1, b0, b1, c0, c1, d0, d1 ... ]
            // We then compute pairs of dynamic-static/sleeping objects in compressed form.
            //    objects = [ ... a0, a1, a2, a3, a4, a0, ..., b0, b1, b2, b3, b4, b0 ... ]
            // where we can determine the start of a new compressed sublist by checking that
            // we are not checknig the pair (x, x)
            var objects = this.persistantObjectsList;

            // Get overlapping pairs of dynamic objects.
            var numDynDyn = dynamicMap.getOverlappingPairs(objects, 0);

            // Get overlapping pairs of static / sleeping <-> dynamic objects.
            var storageIndex = numDynDyn;
            var numPairs;
            limit = rigidBodies.length;
            for (i = 0; i < limit; i += 1) {
                body = rigidBodies[i];
                numPairs = staticMap.getOverlappingNodes(body.extents, objects, storageIndex + 1);
                numPairs += sleepingMap.getOverlappingNodes(body.extents, objects, storageIndex + 1 + numPairs);

                if (numPairs !== 0) {
                    objects[storageIndex] = body;
                    storageIndex += 1 + numPairs;
                    objects[storageIndex] = body;
                    storageIndex += 1;
                }
            }

            // Get overlapping pairs of kinematic <-> sleeping dynamic
            limit = kinematics.length;
            for (i = 0; i < limit; i += 1) {
                body = kinematics[i];

                numPairs = sleepingMap.getOverlappingNodes(body.extents, objects, storageIndex + 1);

                if (numPairs !== 0) {
                    objects[storageIndex] = body;
                    storageIndex += 1 + numPairs;
                    objects[storageIndex] = body;
                    storageIndex += 1;
                }
            }

            // Find contacts for dynamic-dynamic pairs
            // As well as kinematic-dynamic pairs.
            var objectA, objectB;
            for (i = 0; i < numDynDyn; i += 2) {
                objectA = objects[i];
                objectB = objects[i + 1];

                // prevent GC issues
                objects[i] = undefined;
                objects[i + 1] = undefined;
                if (!this.filtered(objectA, objectB)) {
                    if (objectA.id < objectB.id) {
                        this.narrowPhase(objectA.shape, objectB.shape, objectA, objectB);
                    } else {
                        this.narrowPhase(objectB.shape, objectA.shape, objectB, objectA);
                    }
                }
            }

            for (i = numDynDyn; i < storageIndex;) {
                objectA = objects[i];

                // prevent GC issues
                objects[i] = undefined;

                i += 1;
                for (; ;) {
                    objectB = objects[i];

                    //prevent GC issues
                    objects[i] = undefined;
                    i += 1;

                    if (objectA === objectB) {
                        break;
                    }

                    if (!this.filtered(objectA, objectB)) {
                        if (objectA.id < objectB.id) {
                            this.narrowPhase(objectA.shape, objectB.shape, objectA, objectB);
                        } else {
                            this.narrowPhase(objectB.shape, objectA.shape, objectB, objectA);
                        }
                    }
                }
            }
            performance.discrete += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            // Compute islands and perform sleeping.
            preTime = TurbulenzEngine.getTime() * 0.001;
            this.computeSleeping(timeStep);
            performance.sleepComputation += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            // Prestep arbiters
            preTime = TurbulenzEngine.getTime() * 0.001;
            i = 0;
            var arb;
            while (i < arbiters.length) {
                arb = arbiters[i];
                if (!arb.objectA.active && !arb.objectB.active) {
                    arb.active = false;
                    arbiters[i] = arbiters[arbiters.length - 1];
                    arbiters.pop();
                    continue;
                }

                if (arb.refreshContacts()) {
                    arbiters[i] = arbiters[arbiters.length - 1];
                    arbiters.pop();

                    objectA = arb.objectA;
                    objectB = arb.objectB;

                    var bodyArbiters = objectA.arbiters;
                    bodyArbiters[bodyArbiters.indexOf(arb)] = bodyArbiters[bodyArbiters.length - 1];
                    bodyArbiters.pop();

                    bodyArbiters = objectB.arbiters;
                    bodyArbiters[bodyArbiters.indexOf(arb)] = bodyArbiters[bodyArbiters.length - 1];
                    bodyArbiters.pop();

                    if ((objectA.contactCallbacks && objectA.contactCallbacks.onRemovedContacts) || (objectB.contactCallbacks && objectB.contactCallbacks.onRemovedContacts)) {
                        this.contactCallbackRemovedArbiters.push(arb);
                    } else {
                        WebGLPhysicsArbiter.deallocate(arb);
                    }

                    continue;
                }

                arb.preStep(timeStepRatio, timeStep);

                i += 1;
            }
            performance.prestepContacts += (TurbulenzEngine.getTime() * 0.001 - preTime);

            preTime = TurbulenzEngine.getTime() * 0.001;

            // Prestep constraints
            limit = constraints.length;
            for (i = 0; i < limit; i += 1) {
                constraints[i].preStep(timeStepRatio, timeStep);
            }
            performance.prestepConstraints += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            preTime = TurbulenzEngine.getTime() * 0.001;

            // Integrate velocities, apply gravity
            limit = rigidBodies.length;
            for (i = 0; i < limit; i += 1) {
                body = rigidBodies[i];
                body.integrateVelocity(gravity, timeStep);
            }

            performance.integrateVelocities += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            preTime = TurbulenzEngine.getTime() * 0.001;

            // Warmstart arbiters
            limit = arbiters.length;
            for (i = 0; i < limit; i += 1) {
                arbiters[i].applyCachedImpulses();
            }
            performance.warmstartContacts += (TurbulenzEngine.getTime() * 0.001 - preTime);

            preTime = TurbulenzEngine.getTime() * 0.001;

            // Warmstart constraints
            limit = constraints.length;
            for (i = 0; i < limit; i += 1) {
                constraints[i].applyCachedImpulses();
            }
            performance.warmstartConstraints += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            preTime = TurbulenzEngine.getTime() * 0.001;

            // Physics iterations
            var numIterations = 10;
            for (i = 0; i < numIterations; i += 1) {
                limit = arbiters.length;
                for (j = 0; j < limit; j += 1) {
                    arbiters[j].computeAndApplyImpulses();
                }

                limit = constraints.length;
                for (j = 0; j < limit; j += 1) {
                    constraints[j].computeAndApplyImpulses();
                }
            }

            numIterations = 3;
            limit = arbiters.length;
            for (i = 0; i < numIterations; i += 1) {
                for (j = 0; j < limit; j += 1) {
                    arbiters[j].computeAndApplyBiasImpulses();
                }
            }

            performance.physicsIterations += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            // Apply bias velocities to get start transform for sweeps.
            // Then integrate positions to get end transform for sweeps.
            // Syncing bodies into broadphase with swept AABB.
            var unfrozen = this.persistantObjectsList2;
            var numUnfrozen = 0;

            preTime = TurbulenzEngine.getTime() * 0.001;
            limit = rigidBodies.length;
            var radius;

            var timeStepSq = timeStep * timeStep;

            var xform0, xform1;
            for (i = 0; i < limit; i += 1) {
                body = rigidBodies[i];
                body.applyBiasVelocities(timeStep);
                body.integratePosition(timeStep);

                if (!body.isActiveVelocity(WebGLPhysicsConfig.CONTINUOUS_LINEAR_SQ / timeStep, WebGLPhysicsConfig.CONTINUOUS_ANGULAR_SQ / timeStep)) {
                    body.sweepFrozen = true;
                    body.bullet = false;
                    continue;
                }

                // cached for triangle mesh lookups.
                //VMath.m43Copy(body.transform, body.endTransform);
                xform0 = body.transform;
                xform1 = body.endTransform;
                xform1[0] = xform0[0];
                xform1[1] = xform0[1];
                xform1[2] = xform0[2];
                xform1[3] = xform0[3];
                xform1[4] = xform0[4];
                xform1[5] = xform0[5];
                xform1[6] = xform0[6];
                xform1[7] = xform0[7];
                xform1[8] = xform0[8];
                xform1[9] = xform0[9];
                xform1[10] = xform0[10];
                xform1[11] = xform0[11];

                // determine if body should be a bullet.
                radius = body.shape.radius * WebGLPhysicsConfig.CONTINUOUS_LINEAR_BULLET;

                var vel = body.velocity;
                var vlsq = ((vel[0] * vel[0]) + (vel[1] * vel[1]) + (vel[2] * vel[2])) * timeStepSq;
                var wlsq = ((vel[3] * vel[3]) + (vel[4] * vel[4]) + (vel[5] * vel[5])) * timeStepSq;

                body.bullet = vlsq > (radius * radius) || wlsq > WebGLPhysicsConfig.CONTINUOUS_ANGULAR_BULLET;

                extents = body.extents;
                body.calculateSweptExtents(extents);
                dynamicMap.update(body, extents);

                body.sweepFrozen = false;
                unfrozen[numUnfrozen] = body;
                numUnfrozen += 1;
            }

            limit = kinematics.length;
            for (i = 0; i < limit; i += 1) {
                body = kinematics[i];

                VMath.m43Copy(body.transform, body.startTransform);
                body.integratePosition(timeStep);

                extents = body.extents;
                body.calculateSweptExtents(extents);
                dynamicMap.update(body, extents);
            }

            performance.integratePositions += (TurbulenzEngine.getTime() * 0.001 - preTime);

            // ####################################################################
            preTime = TurbulenzEngine.getTime() * 0.001;

            // We must finalize the broadphase once more.
            // Any objects that have gone to sleep (or been woken up) will have effected
            // the static map.
            // And every dynamic object has been updated in dynamic map with its swept
            // extents for continuous collisions and absolutely must be finalized.
            staticMap.finalize();
            dynamicMap.finalize();
            sleepingMap.finalize();

            // Continuous collision detection.
            var slop = WebGLPhysicsConfig.CONTINUOUS_SLOP + WebGLPhysicsConfig.CONTACT_SLOP;

            var events = this.persistantTOIEventList;
            var numEvents = 0;
            var toi;

            // Determine pairs of dynamics with one being a bullet that must be checked for collisions
            numDynDyn = dynamicMap.getOverlappingPairs(objects, 0);
            for (i = 0; i < numDynDyn; i += 2) {
                objectA = objects[i];
                objectB = objects[i + 1];

                // prevent possible GC issues.
                objects[i] = undefined;
                objects[i + 1] = undefined;

                if (!((objectA.bullet || objectA.kinematic) || (objectB.bullet || objectB.kinematic)) || (objectA.sweepFrozen && objectB.sweepFrozen) || this.filtered(objectA, objectB)) {
                    continue;
                }

                if (objectA.kinematic || objectB.kinematic) {
                    if (objectA.kinematic) {
                        numEvents = this.performStaticTOIBase(slop, timeStep, events, numEvents, objectB, objectA);
                    } else {
                        numEvents = this.performStaticTOIBase(slop, timeStep, events, numEvents, objectA, objectB);
                    }
                } else {
                    toi = WebGLPhysicsTOIEvent.allocate();
                    toi.objectA = objectA;
                    toi.objectB = objectB;
                    toi.shapeA = objectA.shape;
                    toi.shapeB = objectB.shape;

                    this.dynamicSweep(toi, timeStep, 0, slop);

                    // don't cull non-existant toi's for dynamic-dynamic.
                    // freezing of either object will impact whether a toi
                    // is able to be computed or not. miss too many collisions
                    // by culling too early here.
                    toi.frozenA = objectA.sweepFrozen;
                    toi.frozenB = objectB.sweepFrozen;

                    events[numEvents] = toi;
                    numEvents += 1;
                }
            }

            for (i = 0; i < numUnfrozen; i += 1) {
                objectA = unfrozen[i];
                numPairs = staticMap.getOverlappingNodes(objectA.extents, objects, 0);
                numPairs += sleepingMap.getOverlappingNodes(objectA.extents, objects, numPairs);
                for (j = 0; j < numPairs; j += 1) {
                    objectB = objects[j];

                    // prevent possible GC issues
                    objects[j] = undefined;

                    if (this.filtered(objectA, objectB)) {
                        continue;
                    }

                    numEvents = this.performStaticTOIBase(slop, timeStep, events, numEvents, objectA, objectB);
                }
            }

            // Time to begin!
            var curTimeAlpha = 0;
            while (curTimeAlpha < 1 && numEvents > 0) {
                var minTOI = null;
                var minIndex;

                for (i = 0; i < numEvents;) {
                    toi = events[i];

                    objectA = toi.objectA;
                    objectB = toi.objectB;

                    if (objectA.sweepFrozen && objectB.sweepFrozen) {
                        numEvents -= 1;
                        if (i !== numEvents) {
                            events[i] = events[numEvents];

                            // prevent possible GC issues
                            events[numEvents] = undefined;
                        }
                        WebGLPhysicsTOIEvent.deallocate(toi);
                        continue;
                    }

                    if ((toi.frozenA !== objectA.sweepFrozen) || (toi.frozenB !== objectB.sweepFrozen)) {
                        // Recompute TOI.
                        toi.frozenA = objectA.sweepFrozen;
                        toi.frozenB = objectB.sweepFrozen;

                        if (toi.frozenA) {
                            toi.objectA = objectB;
                            toi.objectB = objectA;
                            toi.shapeA = objectB.shape;
                            toi.shapeB = objectA.shape;
                            toi.frozenA = false;
                            toi.frozenB = true;
                        }
                        this.staticSweep(toi, timeStep, curTimeAlpha, slop);

                        if (toi.toi === undefined) {
                            numEvents -= 1;
                            if (i !== numEvents) {
                                events[i] = events[numEvents];

                                // prevent possible GC issues
                                events[numEvents] = undefined;
                            }
                            WebGLPhysicsTOIEvent.deallocate(toi);
                            continue;
                        }
                    }

                    if (toi.toi !== undefined && (minTOI === null || (toi.toi < minTOI.toi))) {
                        minTOI = toi;
                        minIndex = i;
                    }

                    i += 1;
                }

                if (minTOI === null) {
                    break;
                }

                // remove TOI Event from list.
                numEvents -= 1;
                if (minIndex !== numEvents) {
                    events[minIndex] = events[numEvents];

                    // prevent possible GC issues
                    events[numEvents] = undefined;
                }

                // Advance time alpha.
                curTimeAlpha = minTOI.toi;

                // Freeze objects at TOI.
                objectA = minTOI.objectA;
                objectB = minTOI.objectB;
                if (!objectA.collisionObject) {
                    if (!objectA.sweepFrozen) {
                        objectA.integratePosition(timeStep * curTimeAlpha);
                        objectA.sweepFrozen = true;
                    }
                    if (objectA.permitSleep && !objectA.active) {
                        this.wakeBody(objectA);
                    }
                }
                if (!objectB.collisionObject) {
                    if (!objectB.sweepFrozen) {
                        objectB.integratePosition(timeStep * curTimeAlpha);
                        objectB.sweepFrozen = true;
                    }
                    if (objectB.permitSleep && !objectB.active) {
                        this.wakeBody(objectB);
                    }
                }

                if (objectA.id > objectB.id) {
                    var tmp = objectA;
                    objectA = objectB;
                    objectB = tmp;

                    var tmpv = minTOI.closestA;
                    minTOI.closestA = minTOI.closestB;
                    minTOI.closestB = tmpv;

                    tmpv = minTOI.axis;
                    tmpv[0] = -tmpv[0];
                    tmpv[1] = -tmpv[1];
                    tmpv[2] = -tmpv[2];
                }

                var shapeA = objectA.shape;
                var shapeB = objectB.shape;

                // Find existing arbiter for shape pair.
                // Iterating the smaller list of either object.
                arb = null;
                var arbitersA = objectA.arbiters;
                var arbitersB = objectB.arbiters;
                var arbs = (arbitersA.length <= arbitersB.length) ? arbitersA : arbitersB;

                var numArbiters = arbs.length;
                for (i = 0; i < numArbiters; i += 1) {
                    var carb = arbs[i];
                    if (carb.shapeA === shapeA && carb.shapeB === shapeB && carb.objectA === objectA && carb.objectB === objectB) {
                        arb = carb;
                        break;
                    }
                }

                // If arbiter does not already exist, create a new one.
                var fresh = (arb === null);
                if (fresh) {
                    arb = WebGLPhysicsArbiter.allocate(shapeA, shapeB, objectA, objectB);
                }

                arb.insertContact(minTOI.closestA, minTOI.closestB, minTOI.axis, minTOI.distance, minTOI.concave);
                if (fresh) {
                    arbiters.push(arb);
                    arb.active = true;
                    objectA.arbiters.push(arb);
                    objectB.arbiters.push(arb);
                }

                if (!((objectA.kinematic && objectA.active) || (objectB.kinematic && objectB.active))) {
                    arb.skipDiscreteCollisions = true;
                }

                WebGLPhysicsTOIEvent.deallocate(minTOI);
            }

            while (numEvents > 0) {
                numEvents -= 1;
                WebGLPhysicsTOIEvent.deallocate(events[numEvents]);
                events[numEvents] = undefined;
            }

            while (numUnfrozen > 0) {
                numUnfrozen -= 1;
                objectA = unfrozen[numUnfrozen];

                // prevent possible GC issues
                unfrozen[numUnfrozen] = undefined;

                if (!objectA.sweepFrozen) {
                    objectA.integratePosition(timeStep);
                }
            }

            performance.continuous += (TurbulenzEngine.getTime() * 0.001 - preTime);
        }

        // Ensure kinematic bodies are moved 'EXACTLY' to their set transform.
        // (Numerical innacuries in integrations).
        limit = kinematics.length;
        for (i = 0; i < limit; i += 1) {
            body = kinematics[i];

            VMath.m43Copy(body.newTransform, body.transform);
            VMath.m43Copy(body.newTransform, body.prevTransform);
        }

        this.updateContactCallbacks();

        this.midStep = false;
    };

    WebGLPrivatePhysicsWorld.prototype.rayTest = function (ray) {
        var group = ray.group;
        var mask = ray.mask;
        if (group === undefined) {
            group = WebGLPhysicsDevice.prototype.FILTER_DYNAMIC;
        }
        if (mask === undefined) {
            mask = WebGLPhysicsDevice.prototype.FILTER_ALL;
        }

        var exclude = ray.exclude;

        // Create parametric ray
        var pRay = {
            origin: ray.from,
            direction: VMath.v3Sub(ray.to, ray.from),
            maxFactor: 1.0
        };

        this.staticSpatialMap.finalize();
        this.dynamicSpatialMap.finalize();
        this.sleepingSpatialMap.finalize();

        function rayCallback(tree, obj, pRay, unusedAABBDistance, upperBound) {
            /*jshint bitwise: false*/
            var actual_obj = obj._public;
            if (actual_obj === exclude || (obj.mask & group) === 0 || (obj.group & mask) === 0) {
                return null;
            }

            /*jshint bitwise: true*/
            pRay.maxFactor = upperBound;
            var resultObj = obj.rayTest(pRay);
            if (resultObj !== null) {
                if (obj.collisionObject) {
                    resultObj.collisionObject = actual_obj;
                    resultObj.body = null;
                } else {
                    resultObj.collisionObject = null;
                    resultObj.body = actual_obj;
                }
            }

            return resultObj;
        }

        var ret = AABBTree.rayTest([this.staticSpatialMap, this.dynamicSpatialMap, this.sleepingSpatialMap], pRay, rayCallback);

        if (ret !== null) {
            delete ret.factor;
        }

        return ret;
    };

    //
    // cache having properties
    //   shapeA
    //   shapeB
    //   axis <-- to be mutated by this function
    //      axis is 'on' object B.
    //   closestA <-- to be populated by this function
    //   closestB <-- to be populated by this function
    WebGLPrivatePhysicsWorld.prototype.contactPairTest = function (cache, xformA, xformB) {
        var axis = cache.axis;
        var shapeA = cache.shapeA;
        var shapeB = cache.shapeB;
        var supportA = cache.closestA;
        var supportB = cache.closestB;

        if (this.contactGJK === undefined) {
            this.contactGJK = WebGLGJKContactSolver.create();
            this.contactEPA = WebGLContactEPA.create();
        }

        if (shapeA.type === "PLANE" || shapeB.type === "PLANE") {
            var planeShape, otherShape;
            var planeXForm, otherXForm;
            if (shapeA.type === "PLANE") {
                planeShape = shapeA;
                planeXForm = xformA;
                otherShape = shapeB;
                otherXForm = xformB;
            } else {
                planeShape = shapeB;
                planeXForm = xformB;
                otherShape = shapeA;
                otherXForm = xformA;
            }

            var A0 = planeXForm[0];
            var A1 = planeXForm[1];
            var A2 = planeXForm[2];
            var A3 = planeXForm[3];
            var A4 = planeXForm[4];
            var A5 = planeXForm[5];
            var A6 = planeXForm[6];
            var A7 = planeXForm[7];
            var A8 = planeXForm[8];
            var A9 = planeXForm[9];
            var A10 = planeXForm[10];
            var A11 = planeXForm[11];

            // local plane normal and distance.
            var n = planeShape.normal;
            var n0 = n[0];
            var n1 = n[1];
            var n2 = n[2];
            var nd = planeShape.distance;

            // transform plane normal into world space.
            var w0 = (n0 * A0) + (n1 * A3) + (n2 * A6);
            var w1 = (n0 * A1) + (n1 * A4) + (n2 * A7);
            var w2 = (n0 * A2) + (n1 * A5) + (n2 * A8);

            A0 = otherXForm[0];
            A1 = otherXForm[1];
            A2 = otherXForm[2];
            A3 = otherXForm[3];
            A4 = otherXForm[4];
            A5 = otherXForm[5];
            A6 = otherXForm[6];
            A7 = otherXForm[7];
            A8 = otherXForm[8];
            var B9 = otherXForm[9];
            var B10 = otherXForm[10];
            var B11 = otherXForm[11];

            // transform plane into shape local space.
            n0 = (A0 * w0) + (A1 * w1) + (A2 * w2);
            n1 = (A3 * w0) + (A4 * w1) + (A5 * w2);
            n2 = (A6 * w0) + (A7 * w1) + (A8 * w2);
            nd += (w0 * (A9 - B9)) + (w1 * (A10 - B10)) + (w2 * (A11 - B11));

            // Find maximum and minimal support points on shape.
            axis[0] = n0;
            axis[1] = n1;
            axis[2] = n2;
            otherShape.localSupportWithoutMargin(axis, supportA);

            axis[0] = -n0;
            axis[1] = -n1;
            axis[2] = -n2;
            otherShape.localSupportWithoutMargin(axis, supportB);

            // Find distance from plane for each support.
            var dot1 = (supportA[0] * n0) + (supportA[1] * n1) + (supportA[2] * n2) - nd;
            var dot2 = (supportB[0] * n0) + (supportB[1] * n1) + (supportB[2] * n2) - nd;

            // Choose closest support to plane for distance computation
            // with margins.
            var seperation, c0, c1, c2;
            if ((dot1 * dot1) < (dot2 * dot2)) {
                c0 = supportA[0];
                c1 = supportA[1];
                c2 = supportA[2];
                seperation = dot1;
            } else {
                c0 = supportB[0];
                c1 = supportB[1];
                c2 = supportB[2];
                seperation = dot2;
            }

            if ((seperation < 0) !== ((dot1 * dot2) < 0)) {
                seperation = -seperation;

                // negate normal
                w0 = -w0;
                w1 = -w1;
                w2 = -w2;
            }

            // Take collision margin from seperation.
            var rad = otherShape.collisionRadius;
            var prad = planeShape.collisionRadius;

            // find world-space support point on non-plane shape
            //VMath.m43TransformPoint(otherXForm, closest, closest);
            var a0 = (A0 * c0) + (A3 * c1) + (A6 * c2) + B9;
            var a1 = (A1 * c0) + (A4 * c1) + (A7 * c2) + B10;
            var a2 = (A2 * c0) + (A5 * c1) + (A8 * c2) + B11;

            // find world-space support point on plane shape
            // including collision margin
            var rsep = prad - seperation;
            var p0 = a0 + (w0 * rsep);
            var p1 = a1 + (w1 * rsep);
            var p2 = a2 + (w2 * rsep);

            // apply collision margin to non-plane support.
            a0 -= (w0 * rad);
            a1 -= (w1 * rad);
            a2 -= (w2 * rad);

            // apply collision radius to seperation.
            seperation -= rad + prad;

            if (shapeA.type === "PLANE") {
                axis[0] = -w0;
                axis[1] = -w1;
                axis[2] = -w2;
                supportA[0] = p0;
                supportA[1] = p1;
                supportA[2] = p2;
                supportB[0] = a0;
                supportB[1] = a1;
                supportB[2] = a2;
            } else {
                axis[0] = w0;
                axis[1] = w1;
                axis[2] = w2;
                supportA[0] = a0;
                supportA[1] = a1;
                supportA[2] = a2;
                supportB[0] = p0;
                supportB[1] = p1;
                supportB[2] = p2;
            }

            return seperation;
        } else {
            var gjk = this.contactGJK;
            var distance = gjk.evaluate(cache, xformA, xformB);
            if (distance === undefined) {
                distance = this.contactEPA.evaluate(gjk.simplex, cache, xformA, xformB);
            }

            if (distance !== undefined) {
                var axis0 = axis[0];
                var axis1 = axis[1];
                var axis2 = axis[2];

                var radiusA = shapeA.collisionRadius;
                var radiusB = shapeB.collisionRadius;

                supportA[0] -= axis0 * radiusA;
                supportA[1] -= axis1 * radiusA;
                supportA[2] -= axis2 * radiusA;

                supportB[0] += axis0 * radiusB;
                supportB[1] += axis1 * radiusB;
                supportB[2] += axis2 * radiusB;

                return (distance - radiusA - radiusB);
            } else {
                return undefined;
            }
        }
    };

    // callback of the form HitResult -> Bool if callback is
    // undefined, then a callback of function (x) { return true; } is
    // implied.
    //
    // TODO: add type of callback?  { (rayHit: RayHit): boolean; }; ?
    WebGLPrivatePhysicsWorld.prototype.convexSweepTest = function (params, callback) {
        if (this.sweepCache === undefined) {
            this.sweepCache = {
                axis: VMath.v3BuildZero(),
                shapeA: null,
                shapeB: null,
                closestA: VMath.v3BuildZero(),
                closestB: VMath.v3BuildZero()
            };

            // fake triangle shape for triangle meshes!
            this.sweepTriangle = WebGLPhysicsTriangleShape.allocate();

            this.sweepDelta = VMath.v3BuildZero();

            this.sweepFromExtents = new Float32Array(6);
            this.sweepToExtents = new Float32Array(6);
            this.sweepExtents = new Float32Array(6);

            // fake body used to compute shape extents in triangle mesh coordinate systems.
            this.sweepFakeBody = {
                shape: null,
                transform: null
            };
            this.sweepTransform = VMath.m43BuildIdentity();
            this.sweepTransform2 = VMath.m43BuildIdentity();
        }

        var cache = this.sweepCache;
        var triangle = this.sweepTriangle;
        var delta = this.sweepDelta;
        var fromExtents = this.sweepFromExtents;
        var toExtents = this.sweepToExtents;
        var extents = this.sweepExtents;
        var fakeBody = this.sweepFakeBody;
        var transform = this.sweepTransform;
        var transform2 = this.sweepTransform2;

        var that = this;

        // sweep shapeA linearlly from 'from' transform, through delta vector
        // against shapeB with transform 'transform' up to a maximum
        // distance of upperBound
        function staticSweep(shapeA, cpos, delta, shapeB, transform, upperBound) {
            var delta0 = delta[0];
            var delta1 = delta[1];
            var delta2 = delta[2];

            var axis = cache.axis;
            var supportA = cache.closestA;
            var supportB = cache.closestB;

            //VMath.v3Neg(delta, cache.axis);
            axis[0] = -delta0;
            axis[1] = -delta1;
            axis[2] = -delta2;

            cache.shapeA = shapeA;
            cache.shapeB = shapeB;

            var distance = 0;

            var curIter = 0;
            var maxIter = 100;
            var contactDistance;

            var previousDistance = Number.MAX_VALUE;
            var intersected = false;
            for (; ;) {
                var nextContact = that.contactPairTest(cache, cpos, transform);

                if (nextContact === undefined || nextContact < WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                    if (contactDistance !== undefined || nextContact !== undefined) {
                        if (contactDistance === undefined) {
                            contactDistance = nextContact;
                        }
                        intersected = true;
                    }
                    break;
                }

                if ((nextContact - previousDistance) >= 1) {
                    break;
                }
                previousDistance = nextContact;

                // distance to advance object.
                //var dot = VMath.v3Dot(delta, VMath.v3Sub(nextContact.closestB, nextContact.closestA));
                var d0 = supportB[0] - supportA[0];
                var d1 = supportB[1] - supportA[1];
                var d2 = supportB[2] - supportA[2];
                var dot = (delta0 * d0) + (delta1 * d1) + (delta2 * d2);

                if (dot <= WebGLPhysicsConfig.COPLANAR_THRESHOLD) {
                    break;
                }

                var gap = (nextContact * nextContact) / dot;
                distance += gap;
                if (distance >= upperBound) {
                    contactDistance = undefined;
                    break;
                }

                contactDistance = nextContact;
                cpos[9] += (delta0 * gap);
                cpos[10] += (delta1 * gap);
                cpos[11] += (delta2 * gap);

                if (contactDistance <= WebGLPhysicsConfig.GJK_EPA_DISTANCE_THRESHOLD) {
                    intersected = true;
                    break;
                }

                // Max iteration cutoff.
                curIter += 1;
                if (curIter > maxIter) {
                    break;
                }
            }

            if (contactDistance === undefined || !intersected) {
                return null;
            } else {
                return {
                    hitPoint: VMath.v3Copy(supportB),
                    hitNormal: VMath.v3Copy(axis),
                    distance: distance
                };
            }
        }

        var shape = params.shape._private;
        var from = params.from;
        var to = params.to;

        //var delta = VMath.v3Sub(VMath.m43Pos(to), VMath.m43Pos(from));
        var d0 = (to[9] - from[9]);
        var d1 = (to[10] - from[10]);
        var d2 = (to[11] - from[11]);

        //var upperBound = VMath.v3Length(delta);
        var upperBound = Math.sqrt((d0 * d0) + (d1 * d1) + (d2 * d2));

        //VMath.v3Normalize(delta, delta);
        var scale = 1 / upperBound;
        delta[0] = d0 * scale;
        delta[1] = d1 * scale;
        delta[2] = d2 * scale;

        var group = (params.group === undefined) ? WebGLPhysicsDevice.prototype.FILTER_DYNAMIC : params.group;
        var mask = (params.mask === undefined) ? WebGLPhysicsDevice.prototype.FILTER_ALL : params.mask;
        var exclude = params.exclude;

        // Find AABB encompassing swept shape
        fakeBody.shape = shape;
        fakeBody.transform = from;
        WebGLPhysicsPrivateBody.prototype.calculateExtents.call(fakeBody, fromExtents);

        fakeBody.transform = to;
        WebGLPhysicsPrivateBody.prototype.calculateExtents.call(fakeBody, toExtents);

        //var extents = VMath.aabbUnion(fromExtents, toExtents);
        extents[0] = (fromExtents[0] < toExtents[0] ? fromExtents[0] : toExtents[0]);
        extents[1] = (fromExtents[1] < toExtents[1] ? fromExtents[1] : toExtents[1]);
        extents[2] = (fromExtents[2] < toExtents[2] ? fromExtents[2] : toExtents[2]);
        extents[3] = (fromExtents[3] > toExtents[3] ? fromExtents[3] : toExtents[3]);
        extents[4] = (fromExtents[4] > toExtents[4] ? fromExtents[4] : toExtents[4]);
        extents[5] = (fromExtents[5] > toExtents[5] ? fromExtents[5] : toExtents[5]);

        // Find all objects intersecting swept shape AABB.
        this.staticSpatialMap.finalize();
        this.dynamicSpatialMap.finalize();
        this.sleepingSpatialMap.finalize();

        var objects = this.persistantObjectsList;
        var triangles = this.persistantTrianglesList;
        var staticCount = this.staticSpatialMap.getOverlappingNodes(extents, objects, 0);
        staticCount += this.dynamicSpatialMap.getOverlappingNodes(extents, objects, staticCount);
        var limit = staticCount + this.sleepingSpatialMap.getOverlappingNodes(extents, objects, staticCount);

        var minResult = null;
        var i, j;
        for (i = 0; i < limit; i += 1) {
            var object = objects[i];

            // Prevent GC issues from persistant list.
            objects[i] = undefined;

            /*jshint bitwise: false*/
            // TODO: remove cast
            var actual_object = (object)._public;
            if (actual_object === exclude || object.shape === shape || (object.mask & group) === 0 || (object.group & mask) === 0) {
                continue;
            }

            /*jshint bitwise: true*/
            var result;
            var collisionShape = object.shape;
            if (collisionShape.type === "TRIANGLE_MESH") {
                // TODO: remove cast and fix
                var triangleArray = (collisionShape).triangleArray;
                triangle.triangleArray = triangleArray;

                // TODO: remove cast and fix
                triangle.collisionRadius = (collisionShape).collisionRadius;

                var numTriangles;
                if (triangleArray.spatialMap) {
                    // Find AABB encompassing swept shape, in local coordinate system of triangle mesh.
                    VMath.m43InverseOrthonormal(object.transform, transform2);
                    VMath.m43Mul(from, transform2, transform);

                    fakeBody.transform = transform;
                    WebGLPhysicsPrivateBody.prototype.calculateExtents.call(fakeBody, fromExtents);

                    VMath.m43Mul(to, transform2, transform);
                    WebGLPhysicsPrivateBody.prototype.calculateExtents.call(fakeBody, toExtents);

                    //var extents = VMath.aabbUnion(fromExtents, toExtents);
                    extents[0] = (fromExtents[0] < toExtents[0] ? fromExtents[0] : toExtents[0]);
                    extents[1] = (fromExtents[1] < toExtents[1] ? fromExtents[1] : toExtents[1]);
                    extents[2] = (fromExtents[2] < toExtents[2] ? fromExtents[2] : toExtents[2]);
                    extents[3] = (fromExtents[3] > toExtents[3] ? fromExtents[3] : toExtents[3]);
                    extents[4] = (fromExtents[4] > toExtents[4] ? fromExtents[4] : toExtents[4]);
                    extents[5] = (fromExtents[5] > toExtents[5] ? fromExtents[5] : toExtents[5]);

                    numTriangles = triangleArray.spatialMap.getOverlappingNodes(extents, triangles, 0);
                    for (j = 0; j < numTriangles; j += 1) {
                        triangle.index = triangles[j].index;

                        // avoid GC problems of persistant array.
                        triangles[j] = undefined;

                        VMath.m43Copy(from, transform2);
                        result = staticSweep(shape, transform2, delta, triangle, object.transform, upperBound);
                        if (result) {
                            result.collisionObject = actual_object;
                            result.body = null;

                            if (!callback || callback(result)) {
                                minResult = result;
                                upperBound = result.distance;
                            }
                        }
                    }
                } else {
                    numTriangles = triangleArray.numTriangles;
                    for (j = 0; j < numTriangles; j += 1) {
                        triangle.index = (j * WebGLPhysicsPrivateTriangleArray.prototype.TRIANGLE_SIZE);
                        VMath.m43Copy(from, transform2);
                        result = staticSweep(shape, transform2, delta, triangle, object.transform, upperBound);
                        if (result) {
                            result.collisionObject = actual_object;
                            result.body = null;

                            if (!callback || callback(result)) {
                                minResult = result;
                                upperBound = result.distance;
                            }
                        }
                    }
                }
            } else {
                VMath.m43Copy(from, transform2);
                result = staticSweep(shape, transform2, delta, collisionShape, object.transform, upperBound);
                if (result) {
                    if ((object).collisionObject) {
                        result.collisionObject = actual_object;
                        result.body = null;
                    } else {
                        result.collisionObject = null;
                        result.body = actual_object;
                    }

                    if (!callback || callback(result)) {
                        minResult = result;
                        upperBound = result.distance;
                    }
                }
            }

            if (upperBound < 1e-4) {
                for (j = i; j < limit; j += 1) {
                    objects[j] = undefined;
                }

                break;
            }
        }

        if (minResult) {
            // delete additional property
            delete minResult.distance;
        }

        return minResult;
    };

    WebGLPrivatePhysicsWorld.prototype.addBody = function (body) {
        if (body.world) {
            return false;
        }

        body.world = this;
        if (body.collisionObject && !body.kinematic) {
            this.collisionObjects.push(body);
            this.syncBody(body);
            return true;
        }

        if (body.kinematic) {
            this.kinematicBodies.push(body);
        } else {
            this.rigidBodies.push(body);
        }

        var addSleeping = !body.active;
        body.previouslyActive = true;
        body.active = false;

        // Prepare body for disjoint set forest algorithm
        // in computeSleeping
        body.islandRoot = body;
        body.islandRank = 0;

        if (!addSleeping) {
            this.wakeBody(body);
        } else {
            this.syncBody(body);
        }

        return true;
    };

    WebGLPrivatePhysicsWorld.prototype.removeBody = function (body) {
        if (body.world !== this) {
            return false;
        }

        var list, activeList;
        if (body.collisionObject && !body.kinematic) {
            list = this.collisionObjects;
        } else if (body.kinematic) {
            list = this.kinematicBodies;
            activeList = this.activeKinematics;
        } else {
            list = this.rigidBodies;
            activeList = this.activeBodies;
        }

        body.world = null;
        list[list.indexOf(body)] = list[list.length - 1];
        list.pop();

        if (activeList && body.active) {
            activeList[activeList.indexOf(body)] = activeList[activeList.length - 1];
            activeList.pop();
            this.dynamicSpatialMap.remove(body);
        } else if (body.collisionObject && !body.kinematic) {
            this.staticSpatialMap.remove(body);
        } else {
            this.sleepingSpatialMap.remove(body);
        }

        this.removeArbitersFromObject(body);

        this.removeFromContactCallbacks(body);

        var island = body.island;
        if (island) {
            var bodies = island.bodies;
            var bodyIndex = bodies.indexOf(body);
            if (bodyIndex !== -1) {
                bodies[bodyIndex] = bodies[bodies.length - 1];
                bodies.pop();
            }
            body.island = null;
        }

        return true;
    };

    WebGLPrivatePhysicsWorld.prototype.addConstraint = function (constraint) {
        if (constraint.world) {
            return false;
        }

        constraint.world = this;
        this.constraints.push(constraint);

        if (constraint.bodyA) {
            constraint.bodyA.constraints.push(constraint);
        }
        if (constraint.bodyB) {
            constraint.bodyB.constraints.push(constraint);
        }

        var addSleeping = !constraint.active;
        constraint.active = false;

        // Prepare constraint for disjoint set forest algorithm
        // in computeSleeping
        constraint.islandRoot = constraint;
        constraint.islandRank = 0;

        if (!addSleeping) {
            this.wakeConstraint(constraint);
        }

        return true;
    };

    WebGLPrivatePhysicsWorld.prototype.removeConstraint = function (constraint) {
        if (constraint.world !== this) {
            return false;
        }

        constraint.world = null;

        var list = this.constraints;
        list[list.indexOf(constraint)] = list[list.length - 1];
        list.pop();

        if (constraint.bodyA) {
            list = constraint.bodyA.constraints;
            list[list.indexOf(constraint)] = list[list.length - 1];
            list.pop();
        }
        if (constraint.bodyB) {
            list = constraint.bodyA.constraints;
            list[list.indexOf(constraint)] = list[list.length - 1];
            list.pop();
        }

        if (constraint.active) {
            list = this.activeConstraints;
            list[list.indexOf(constraint)] = list[list.length - 1];
            list.pop();
        }

        var island = constraint.island;
        if (island) {
            var constraints = island.constraints;
            var constraintIndex = constraints.indexOf(constraint);
            if (constraintIndex !== -1) {
                constraints[constraintIndex] = constraints[constraints.length - 1];
                constraints.pop();
            }
            constraint.island = null;
        }

        return true;
    };

    WebGLPrivatePhysicsWorld.prototype.flush = function () {
        while (this.rigidBodies.length > 0) {
            this.removeBody(this.rigidBodies[0]);
        }

        while (this.collisionObjects.length > 0) {
            this.removeBody(this.collisionObjects[0]);
        }

        while (this.kinematicBodies.length > 0) {
            this.removeBody(this.kinematicBodies[0]);
        }

        while (this.constraints.length > 0) {
            this.removeConstraint(this.constraints[0]);
        }

        this.timeStamp = 0;
    };

    WebGLPrivatePhysicsWorld.prototype.removeArbitersFromObject = function (object) {
        var arbiters = object.arbiters;
        var worldArbiters = this.activeArbiters;
        while (arbiters.length > 0) {
            var arb = arbiters.pop();

            // Remove from other object also.
            var bodyArbiters = (arb.objectA === object) ? arb.objectB.arbiters : arb.objectA.arbiters;
            bodyArbiters[bodyArbiters.indexOf(arb)] = bodyArbiters[bodyArbiters.length - 1];
            bodyArbiters.pop();

            if (arb.active) {
                worldArbiters[worldArbiters.indexOf(arb)] = worldArbiters[worldArbiters.length - 1];
                worldArbiters.pop();
            }

            while (arb.contacts.length > 0) {
                var contact = arb.contacts.pop();
                WebGLPhysicsContact.deallocate(contact);
            }

            WebGLPhysicsArbiter.deallocate(arb);
        }
    };

    WebGLPrivatePhysicsWorld.prototype.removeFromContactCallbacks = function (object) {
        var contactCallbackObjects = this.contactCallbackObjects;
        var numObjects = contactCallbackObjects.length;
        var n;
        for (n = 0; n < numObjects; n += 1) {
            if (contactCallbackObjects[n] === object) {
                numObjects -= 1;
                if (n < numObjects) {
                    contactCallbackObjects[n] = contactCallbackObjects[numObjects];
                }
                contactCallbackObjects.length = numObjects;
                break;
            }
        }
        object.addedToContactCallbacks = false;
    };

    WebGLPrivatePhysicsWorld.prototype.updateContactCallbacks = function () {
        var contactCallbackObjects = this.contactCallbackObjects;
        var numObjects = contactCallbackObjects.length;
        var publicContacts = WebGLPhysicsContact.publicContacts;
        var callbackContacts = WebGLPhysicsContact.callbackContacts;
        var arbiter, objectA, objectB, contactCallbacksA, contactCallbacksB;
        var n = 0;
        while (n < numObjects) {
            var object = contactCallbackObjects[n];
            var arbiters = object.arbiters;
            var numArbiters = arbiters.length;
            if (0 === numArbiters) {
                object.contactCallbacks.added = false;
                numObjects -= 1;
                if (n < numObjects) {
                    contactCallbackObjects[n] = contactCallbackObjects[numObjects];
                }
                contactCallbackObjects.length = numObjects;
            } else {
                var i, j;
                for (i = 0; i < numArbiters; i += 1) {
                    arbiter = arbiters[i];
                    if (0 !== arbiter.contactFlags) {
                        var contacts = arbiter.contacts;
                        var numContacts = contacts.length;

                        while (publicContacts.length < numContacts) {
                            publicContacts[publicContacts.length] = WebGLPhysicsPublicContact.create();
                        }

                        callbackContacts.length = numContacts;
                        for (j = 0; j < numContacts; j += 1) {
                            var publicContact = publicContacts[j];
                            publicContact._private = contacts[j];
                            callbackContacts[j] = publicContact;
                        }

                        objectA = arbiter.objectA;
                        objectB = arbiter.objectB;

                        contactCallbacksA = objectA.contactCallbacks;
                        contactCallbacksB = objectB.contactCallbacks;

                        if (arbiter.contactFlags & 1) {
                            if (null !== contactCallbacksA && contactCallbacksA.onAddedContacts) {
                                contactCallbacksA.onAddedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                            if (null !== contactCallbacksB && contactCallbacksB.onAddedContacts) {
                                contactCallbacksB.onAddedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                        }

                        if (arbiter.contactFlags & 2) {
                            if (null !== contactCallbacksA && contactCallbacksA.onProcessedContacts) {
                                contactCallbacksA.onProcessedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                            if (null !== contactCallbacksB && contactCallbacksB.onProcessedContacts) {
                                contactCallbacksB.onProcessedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                        }

                        if (arbiter.contactFlags & 4) {
                            if (null !== contactCallbacksA && contactCallbacksA.onRemovedContacts) {
                                contactCallbacksA.onRemovedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                            if (null !== contactCallbacksB && contactCallbacksB.onRemovedContacts) {
                                contactCallbacksB.onRemovedContacts(objectA._public, objectB._public, callbackContacts);
                            }
                        }

                        arbiter.contactFlags = 0;

                        for (j = 0; j < numContacts; j += 1) {
                            contacts[j][51] = 0;
                        }
                    }
                }
                n += 1;
            }
        }

        // Callbacks for pairs no longer touching
        var contactCallbackRemovedArbiters = this.contactCallbackRemovedArbiters;
        numObjects = contactCallbackRemovedArbiters.length;
        callbackContacts.length = 0;
        for (n = 0; n < numObjects; n += 1) {
            arbiter = contactCallbackRemovedArbiters[n];

            objectA = arbiter.objectA;
            objectB = arbiter.objectB;

            contactCallbacksA = objectA.contactCallbacks;
            contactCallbacksB = objectB.contactCallbacks;

            if (null !== contactCallbacksA && contactCallbacksA.onRemovedContacts) {
                contactCallbacksA.onRemovedContacts(objectA._public, objectB._public, callbackContacts);
            }
            if (null !== contactCallbacksB && contactCallbacksB.onRemovedContacts) {
                contactCallbacksB.onRemovedContacts(objectA._public, objectB._public, callbackContacts);
            }

            WebGLPhysicsArbiter.deallocate(arbiter);
        }
        contactCallbackRemovedArbiters.length = 0;
    };
    WebGLPrivatePhysicsWorld.version = 1;
    return WebGLPrivatePhysicsWorld;
})();

//
// WebGL Physics Device
//
var WebGLPhysicsDevice = (function () {
    function WebGLPhysicsDevice() {
        this.vendor = "Turbulenz";
        this.genObjectId = 0;
    }
    WebGLPhysicsDevice.create = function (/* params */ ) {
        return new WebGLPhysicsDevice();
    };

    WebGLPhysicsDevice.prototype.createDynamicsWorld = function (params) {
        return WebGLPhysicsWorld.create(params);
    };

    WebGLPhysicsDevice.prototype.createPlaneShape = function (params) {
        return WebGLPhysicsPlaneShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createBoxShape = function (params) {
        return WebGLPhysicsBoxShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createSphereShape = function (params) {
        return WebGLPhysicsSphereShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createCapsuleShape = function (params) {
        return WebGLPhysicsCapsuleShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createCylinderShape = function (params) {
        return WebGLPhysicsCylinderShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createConeShape = function (params) {
        return WebGLPhysicsConeShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createTriangleMeshShape = function (params) {
        return WebGLPhysicsTriangleMeshShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createConvexHullShape = function (params) {
        return WebGLPhysicsConvexHullShape.create(params);
    };

    WebGLPhysicsDevice.prototype.createTriangleArray = function (params) {
        return WebGLPhysicsTriangleArray.create(params);
    };

    WebGLPhysicsDevice.prototype.createCollisionObject = function (params) {
        return WebGLPhysicsCollisionObject.create(params);
    };

    WebGLPhysicsDevice.prototype.createRigidBody = function (params) {
        return WebGLPhysicsRigidBody.create(params);
    };

    WebGLPhysicsDevice.prototype.createPoint2PointConstraint = function (params) {
        return WebGLPhysicsPoint2PointConstraint.create(params);
    };

    WebGLPhysicsDevice.prototype.createHingeConstraint = function (params) {
        // TODO: remove the casts
        return (WebGLPhysicsConstraint.create("HINGE", params));
    };

    WebGLPhysicsDevice.prototype.createConeTwistConstraint = function (params) {
        // TODO: remove the casts
        return (WebGLPhysicsConstraint.create("CONETWIST", params));
    };

    WebGLPhysicsDevice.prototype.create6DOFConstraint = function (params) {
        // TODO: remove the casts
        return (WebGLPhysicsConstraint.create("D6", params));
    };

    WebGLPhysicsDevice.prototype.createSliderConstraint = function (params) {
        // TODO: remove the casts
        return (WebGLPhysicsConstraint.create("SLIDER", params));
    };

    WebGLPhysicsDevice.prototype.createCharacter = function (params) {
        return WebGLPhysicsCharacter.create(params);
    };
    WebGLPhysicsDevice.version = 1;
    return WebGLPhysicsDevice;
})();

WebGLPhysicsDevice.prototype.FILTER_DYNAMIC = 1;
WebGLPhysicsDevice.prototype.FILTER_STATIC = 2;
WebGLPhysicsDevice.prototype.FILTER_KINEMATIC = 4;
WebGLPhysicsDevice.prototype.FILTER_DEBRIS = 8;
WebGLPhysicsDevice.prototype.FILTER_TRIGGER = 16;
WebGLPhysicsDevice.prototype.FILTER_CHARACTER = 32;
WebGLPhysicsDevice.prototype.FILTER_PROJECTILE = 64;
WebGLPhysicsDevice.prototype.FILTER_USER_MIN = 128;
WebGLPhysicsDevice.prototype.FILTER_USER_MAX = 0x8000;
WebGLPhysicsDevice.prototype.FILTER_ALL = 0xffff;

// Copyright (c) 2011-2014 Turbulenz Limited
/*global TurbulenzEngine: false*/
/*global SoundTARLoader: false*/
/*global Audio: false*/
/*global VMath: false*/
/*global window: false*/
/*global Uint8Array: false*/

;

;

//
// WebGLSound
//
var WebGLSound = (function () {
    function WebGLSound() {
    }
    WebGLSound.prototype.destroy = function () {
        if (this.buffer) {
            this.buffer = null;
        } else if (this.audio) {
            var src = this.audio.src;
            if (src.indexOf("blob:") === 0) {
                URL.revokeObjectURL(src);
            }
            this.audio = null;
        }
        if (this.blob) {
            this.blob = null;
        }
    };

    WebGLSound.audioLoaded = function (sound, onload) {
        var audio = sound.audio;
        sound.frequency = ((audio).sampleRate || (audio).mozSampleRate || 0);
        sound.channels = ((audio).channels || (audio).mozChannels || 0);
        sound.bitrate = (sound.frequency * sound.channels * 2 * 8);
        sound.length = audio.duration;

        if (audio.buffered && audio.buffered.length) {
            if (isNaN(sound.length) || sound.length === Number.POSITIVE_INFINITY) {
                sound.length = audio.buffered.end(0);
            }

            if (onload) {
                if (sound.length) {
                    onload(sound, 200);
                } else {
                    onload(null, 0);
                }
                onload = null;
            }
        } else {
            // Make sure the data is actually loaded
            var forceLoading = function forceLoadingFn() {
                audio.pause();
                audio.removeEventListener('play', forceLoading, false);
                audio.volume = 1;

                if (onload) {
                    onload(sound, 200);
                    onload = null;
                }
            };
            audio.addEventListener('play', forceLoading, false);
            audio.volume = 0;
            audio.play();
        }
    };

    WebGLSound.create = function (sd, params) {
        var sound = new WebGLSound();

        var soundPath = params.src;

        sound.name = (params.name || soundPath);
        sound.frequency = 0;
        sound.channels = 0;
        sound.bitrate = 0;
        sound.length = 0;
        sound.compressed = (!params.uncompress);

        var onload = params.onload;
        var data = params.data;

        var numSamples, numChannels, samplerRate;

        var audioContext = sd.audioContext;
        if (audioContext && (sound.forceUncompress || params.uncompress)) {
            var buffer;
            if (soundPath) {
                if (!sd.isResourceSupported(soundPath)) {
                    if (onload) {
                        onload(null, 0);
                    }
                    return null;
                }

                var bufferCreated = function bufferCreatedFn(buffer) {
                    if (buffer) {
                        sound.buffer = buffer;
                        sound.frequency = buffer.sampleRate;
                        sound.channels = buffer.numberOfChannels;
                        sound.bitrate = (sound.frequency * sound.channels * 2 * 8);
                        sound.length = buffer.duration;

                        if (onload) {
                            onload(sound, 200);
                        }
                    } else {
                        if (onload) {
                            onload(null, 0);
                        }
                    }
                };

                var bufferFailed = function bufferFailedFn() {
                    if (onload) {
                        onload(null, 0);
                    }
                };

                if (data) {
                    if (audioContext.decodeAudioData) {
                        audioContext.decodeAudioData(data, bufferCreated, bufferFailed);
                    } else {
                        buffer = audioContext.createBuffer(data, false);
                        bufferCreated(buffer);
                    }
                } else {
                    var xhr;
                    if (window.XMLHttpRequest) {
                        xhr = new window.XMLHttpRequest();
                    } else if (window.ActiveXObject) {
                        xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
                    } else {
                        if (onload) {
                            onload(null, 0);
                        }
                        return null;
                    }

                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                                var xhrStatus = xhr.status;
                                var xhrStatusText = (xhrStatus !== 0 && xhr.statusText || 'No connection');
                                var response = xhr.response;

                                if (xhr.getAllResponseHeaders() === "" && !response) {
                                    if (onload) {
                                        onload(null, 0);
                                    }
                                } else if (xhrStatus === 200 || xhrStatus === 0) {
                                    if (audioContext.decodeAudioData) {
                                        audioContext.decodeAudioData(response, bufferCreated, bufferFailed);
                                    } else {
                                        var buffer = audioContext.createBuffer(response, false);
                                        bufferCreated(buffer);
                                    }
                                } else {
                                    if (onload) {
                                        onload(null, xhrStatus);
                                    }
                                }
                            }

                            // break circular reference
                            xhr.onreadystatechange = null;
                            xhr = null;
                        }
                    };
                    xhr.open("GET", soundPath, true);
                    xhr.responseType = "arraybuffer";
                    xhr.setRequestHeader("Content-Type", "text/plain");
                    xhr.send(null);
                }

                return sound;
            } else {
                if (data) {
                    numSamples = data.length;
                    numChannels = (params.channels || 1);
                    samplerRate = params.frequency;

                    var contextSampleRate = Math.min(audioContext.sampleRate, 96000);
                    var c, channel, i, j;

                    if (contextSampleRate === samplerRate) {
                        buffer = audioContext.createBuffer(numChannels, (numSamples / numChannels), samplerRate);

                        for (c = 0; c < numChannels; c += 1) {
                            channel = buffer.getChannelData(c);
                            for (i = c, j = 0; i < numSamples; i += numChannels, j += 1) {
                                channel[j] = data[i];
                            }
                        }
                    } else {
                        var ratio = (samplerRate / contextSampleRate);

                        /*jshint bitwise: false*/
                        var bufferLength = ((numSamples / (ratio * numChannels)) | 0);

                        /*jshint bitwise: true*/
                        buffer = audioContext.createBuffer(numChannels, bufferLength, contextSampleRate);

                        for (c = 0; c < numChannels; c += 1) {
                            channel = buffer.getChannelData(c);
                            for (j = 0; j < bufferLength; j += 1) {
                                /*jshint bitwise: false*/
                                channel[j] = data[c + (((j * ratio) | 0) * numChannels)];
                                /*jshint bitwise: true*/
                            }
                        }
                    }

                    if (buffer) {
                        sound.buffer = buffer;
                        sound.frequency = samplerRate;
                        sound.channels = numChannels;
                        sound.bitrate = (samplerRate * numChannels * 2 * 8);
                        sound.length = (numSamples / (samplerRate * numChannels));

                        if (onload) {
                            onload(sound, 200);
                        }

                        return sound;
                    }
                }
            }
        } else {
            var audio;

            if (soundPath) {
                var extension = soundPath.slice(-3);

                audio = new Audio();
                audio.preload = 'auto';
                audio.autobuffer = true;

                audio.onerror = function loadingSoundFailedFn(/* e */ ) {
                    if (onload) {
                        onload(null, 0);
                        onload = null;
                    }
                };

                sound.audio = audio;

                var checkLoaded = function checkLoadedFn() {
                    if (3 <= audio.readyState) {
                        WebGLSound.audioLoaded(sound, onload);
                        return true;
                    }
                    return false;
                };

                if (data) {
                    var dataArray;
                    if (data instanceof Uint8Array) {
                        dataArray = data;
                    } else {
                        dataArray = new Uint8Array(data);
                    }

                    if (typeof Blob !== "undefined" && typeof URL !== "undefined" && URL.createObjectURL) {
                        var dataBlob;
                        if (dataArray[0] === 79 && dataArray[1] === 103 && dataArray[2] === 103 && dataArray[3] === 83) {
                            extension = 'ogg';
                            dataBlob = new Blob([dataArray], { type: "audio/ogg" });
                        } else if (dataArray[0] === 82 && dataArray[1] === 73 && dataArray[2] === 70 && dataArray[3] === 70) {
                            extension = 'wav';
                            dataBlob = new Blob([dataArray], { type: "audio/wav" });
                        } else {
                            // Assume it's an mp3?
                            extension = 'mp3';
                            dataBlob = new Blob([dataArray], { type: "audio/mpeg" });
                        }
                        /* debug.assert(dataArray.length === dataBlob.size, "Blob constructor does not support typed arrays."); */
                        sound.blob = dataBlob;
                        soundPath = URL.createObjectURL(dataBlob);
                    } else {
                        if (dataArray[0] === 79 && dataArray[1] === 103 && dataArray[2] === 103 && dataArray[3] === 83) {
                            extension = 'ogg';
                            soundPath = 'data:audio/ogg;base64,';
                        } else if (dataArray[0] === 82 && dataArray[1] === 73 && dataArray[2] === 70 && dataArray[3] === 70) {
                            extension = 'wav';
                            soundPath = 'data:audio/wav;base64,';
                        } else {
                            // Assume it's an mp3?
                            extension = 'mp3';
                            soundPath = 'data:audio/mpeg;base64,';
                        }

                        // Mangle data into a data URI
                        soundPath = soundPath + (TurbulenzEngine).base64Encode(dataArray);
                    }
                } else if (typeof URL !== "undefined" && URL.createObjectURL) {
                    if (!sd.supportedExtensions[extension]) {
                        if (onload) {
                            onload(null, 0);
                        }
                        return null;
                    }

                    var xhr = new XMLHttpRequest();
                    xhr.onreadystatechange = function () {
                        if (xhr.readyState === 4) {
                            if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                                var xhrStatus = xhr.status;

                                if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                                    xhrStatus = 200;
                                }

                                if (xhr.getAllResponseHeaders() === "" && !xhr.response) {
                                    if (onload) {
                                        onload(null, 0);
                                    }
                                } else {
                                    if (xhrStatus === 200 || xhrStatus === 0) {
                                        sound.blob = xhr.response;
                                        if (sound.blob.type === 'audio/x-mpg') {
                                            sound.blob = sound.blob.slice(0, sound.blob.size, 'audio/mpeg');
                                        }
                                        audio.src = URL.createObjectURL(sound.blob);

                                        sd.addLoadingSound(checkLoaded);
                                    } else if (onload) {
                                        onload(null, xhrStatus);
                                    }
                                }
                                xhr.onreadystatechange = null;
                                xhr = null;
                            }
                        }
                    };
                    xhr.open('GET', soundPath, true);
                    xhr.responseType = 'blob';
                    xhr.send();

                    return sound;
                }

                if (!sd.supportedExtensions[extension]) {
                    if (onload) {
                        onload(null, 0);
                    }
                    return null;
                }

                audio.src = soundPath;

                sd.addLoadingSound(checkLoaded);

                return sound;
            } else {
                if (data) {
                    audio = new Audio();

                    if (audio.mozSetup) {
                        numSamples = data.length;
                        numChannels = (params.channels || 1);
                        samplerRate = params.frequency;

                        audio.mozSetup(numChannels, samplerRate);

                        sound.data = data;
                        sound.frequency = samplerRate;
                        sound.channels = numChannels;
                        sound.bitrate = (samplerRate * numChannels * 2 * 8);
                        sound.length = (numSamples / (samplerRate * numChannels));

                        sound.audio = audio;

                        if (onload) {
                            onload(sound, 200);
                        }

                        return sound;
                    } else {
                        audio = null;
                    }
                }
            }
        }

        if (onload) {
            onload(null, 0);
        }

        return null;
    };
    WebGLSound.version = 1;
    return WebGLSound;
})();

//
// WebGLSoundSource
//
var WebGLSoundSource = (function () {
    function WebGLSoundSource() {
    }
    // Public API
    WebGLSoundSource.prototype.play = function (sound, seek) {
        if (seek === undefined) {
            seek = 0;
        }

        if (this.sound === sound) {
            return this.seek(seek);
        }

        if (this.playing) {
            this._stop();
        }

        this.sound = sound;

        var soundAudio = (sound).audio;
        if (soundAudio) {
            if ((sound).data) {
                soundAudio = new Audio();
                soundAudio.mozSetup(sound.channels, sound.frequency);
            } else {
                soundAudio = (soundAudio.cloneNode(true));
            }

            this.audio = soundAudio;

            soundAudio.loop = this.looping;

            soundAudio.addEventListener('ended', this.loopAudio, false);

            if (0.05 < seek) {
                try  {
                    soundAudio.currentTime = seek;
                } catch (e) {
                    // It seems there is no reliable way of seeking
                }
            }
        }

        var audioContext = this.audioContext;
        if (audioContext) {
            if (soundAudio) {
                this.createMediaNode(sound, soundAudio);
            } else {
                var bufferNode = this.createBufferNode(sound);

                if (0 < seek) {
                    var buffer = (sound).buffer;
                    if (bufferNode.loop) {
                        bufferNode.start(0, seek, buffer.duration);
                    } else {
                        bufferNode.start(0, seek, (buffer.duration - seek));
                    }
                    this.playStart = (audioContext.currentTime - seek);
                } else {
                    bufferNode.start(0);
                    this.playStart = audioContext.currentTime;
                }
            }
        }

        if (soundAudio) {
            if ((sound).data) {
                (soundAudio).mozWriteAudio((sound).data);
            } else {
                this.updateAudioVolume();
                soundAudio.play();
            }
        }

        this.playing = true;
        this.paused = false;

        this.sd.addPlayingSource(this);

        return true;
    };

    WebGLSoundSource.prototype._stop = function () {
        this.playing = false;
        this.paused = false;
        this.sound = null;

        var audio = this.audio;
        if (audio) {
            this.audio = null;

            var mediaNode = this.mediaNode;
            if (mediaNode) {
                this.mediaNode = null;
                mediaNode.disconnect();
            }

            audio.pause();
            audio.removeEventListener('ended', this.loopAudio, false);
        } else {
            var bufferNode = this.bufferNode;
            if (bufferNode) {
                this.bufferNode = null;
                bufferNode.stop(0);
                bufferNode.disconnect();
            }
        }
    };

    WebGLSoundSource.prototype.stop = function () {
        var playing = this.playing;
        if (playing) {
            this._stop();

            this.sd.removePlayingSource(this);
        }
        return playing;
    };

    WebGLSoundSource.prototype.pause = function () {
        if (this.playing) {
            if (!this.paused) {
                this.paused = true;

                var audio = this.audio;
                if (audio) {
                    audio.pause();
                } else {
                    var bufferNode = this.bufferNode;
                    if (bufferNode) {
                        this.bufferNode = null;
                        this.playPaused = this.audioContext.currentTime;
                        bufferNode.stop(0);
                        bufferNode.disconnect();
                    }
                }

                this.sd.removePlayingSource(this);
            }

            return true;
        }

        return false;
    };

    WebGLSoundSource.prototype.resume = function (seek) {
        if (this.paused) {
            this.paused = false;

            var audio = this.audio;
            if (audio) {
                if (seek !== undefined) {
                    if (0.05 < Math.abs(audio.currentTime - seek)) {
                        try  {
                            audio.currentTime = seek;
                        } catch (e) {
                            // It seems there is no reliable way of seeking
                        }
                    }
                }

                audio.play();
            } else {
                var audioContext = this.audioContext;
                if (audioContext) {
                    if (seek === undefined) {
                        seek = (this.playPaused - this.playStart);
                    }

                    var bufferNode = this.createBufferNode(this.sound);

                    if (0 < seek) {
                        var buffer = this.sound.buffer;
                        if (bufferNode.loop) {
                            bufferNode.start(0, seek, buffer.duration);
                        } else {
                            bufferNode.start(0, seek, (buffer.duration - seek));
                        }
                        this.playStart = (audioContext.currentTime - seek);
                    } else {
                        bufferNode.start(0);
                        this.playStart = audioContext.currentTime;
                    }
                }
            }

            this.sd.addPlayingSource(this);

            return true;
        }

        return false;
    };

    WebGLSoundSource.prototype.rewind = function () {
        if (this.playing) {
            var audio = this.audio;
            if (audio) {
                audio.currentTime = 0;

                return true;
            } else {
                var audioContext = this.audioContext;
                if (audioContext) {
                    var bufferNode = this.bufferNode;
                    if (bufferNode) {
                        bufferNode.stop(0);
                        bufferNode.disconnect();
                    }

                    bufferNode = this.createBufferNode(this.sound);

                    bufferNode.start(0);

                    this.playStart = audioContext.currentTime;

                    return true;
                }
            }
        }

        return false;
    };

    WebGLSoundSource.prototype.seek = function (seek) {
        if (this.playing) {
            var tell = this.tell;
            var delta = Math.abs(tell - seek);
            if (this.looping) {
                delta = Math.min(Math.abs(tell - (this.sound.length + seek)), delta);
            }

            if (0.05 < delta) {
                var audio = this.audio;
                if (audio) {
                    try  {
                        audio.currentTime = seek;
                    } catch (e) {
                        // It seems there is no reliable way of seeking
                    }
                } else {
                    var audioContext = this.audioContext;
                    if (audioContext) {
                        var bufferNode = this.bufferNode;
                        if (bufferNode) {
                            bufferNode.stop(0);
                            bufferNode.disconnect();
                        }

                        bufferNode = this.createBufferNode(this.sound);

                        if (0 < seek) {
                            var buffer = this.sound.buffer;
                            if (bufferNode.loop) {
                                bufferNode.start(0, seek, buffer.duration);
                            } else {
                                bufferNode.start(0, seek, (buffer.duration - seek));
                            }
                            this.playStart = (audioContext.currentTime - seek);
                        } else {
                            bufferNode.start(0);
                            this.playStart = audioContext.currentTime;
                        }
                    }
                }
            }

            return true;
        }

        return false;
    };

    WebGLSoundSource.prototype.clear = function () {
        this.stop();
    };

    WebGLSoundSource.prototype.setAuxiliarySendFilter = function (index, effectSlot, filter) {
        return false;
    };

    WebGLSoundSource.prototype.setDirectFilter = function (filter) {
        return false;
    };

    WebGLSoundSource.prototype.destroy = function () {
        this.stop();

        var gainNode = this.gainNode;
        if (gainNode) {
            this.gainNode = null;
            gainNode.disconnect();
        }

        var pannerNode = this.pannerNode;
        if (pannerNode) {
            this.pannerNode = null;
            pannerNode.disconnect();
        }
    };

    WebGLSoundSource.prototype.updateRelativePositionWebAudio = function (listenerPosition0, listenerPosition1, listenerPosition2) {
        var position = this._position;
        this.pannerNode.setPosition(position[0] + listenerPosition0, position[1] + listenerPosition1, position[2] + listenerPosition2);
    };

    WebGLSoundSource.prototype.updateRelativePositionHTML5 = function (listenerPosition0, listenerPosition1, listenerPosition2) {
        // Change volume depending on distance to listener
        var minDistance = this.minDistance;
        var maxDistance = this.maxDistance;
        var position = this._position;
        var position0 = position[0];
        var position1 = position[1];
        var position2 = position[2];

        var distanceSq;
        if (this.relative) {
            distanceSq = ((position0 * position0) + (position1 * position1) + (position2 * position2));
        } else {
            var delta0 = (listenerPosition0 - position0);
            var delta1 = (listenerPosition1 - position1);
            var delta2 = (listenerPosition2 - position2);
            distanceSq = ((delta0 * delta0) + (delta1 * delta1) + (delta2 * delta2));
        }

        var gainFactor;
        if (distanceSq <= (minDistance * minDistance)) {
            gainFactor = 1;
        } else if (distanceSq >= (maxDistance * maxDistance)) {
            gainFactor = 0;
        } else {
            var distance = Math.sqrt(distanceSq);
            if (this.sd.linearDistance) {
                gainFactor = ((maxDistance - distance) / (maxDistance - minDistance));
            } else {
                gainFactor = minDistance / (minDistance + (this.rollOff * (distance - minDistance)));
            }
        }

        gainFactor *= this.sd.listenerGain;

        if (this.gainFactor !== gainFactor) {
            this.gainFactor = gainFactor;
            this.updateAudioVolume();
        }
    };

    WebGLSoundSource.prototype.createBufferNode = function (sound) {
        var buffer = sound.buffer;

        var bufferNode = this.audioContext.createBufferSource();
        bufferNode.buffer = buffer;
        bufferNode.loop = this.looping;
        if (bufferNode.playbackRate) {
            bufferNode.playbackRate.value = this.pitch;
        }
        bufferNode.connect(this.gainNode);

        if (!bufferNode.start) {
            bufferNode.start = function audioStart(when, offset, duration) {
                if (arguments.length <= 1) {
                    this.noteOn(when);
                } else {
                    this.noteGrainOn(when, offset, duration);
                }
            };
        }

        if (!bufferNode.stop) {
            bufferNode.stop = function audioStop(when) {
                this.noteOff(when);
            };
        }

        this.bufferNode = bufferNode;

        return bufferNode;
    };

    WebGLSoundSource.prototype.createMediaNode = function (sound, audio) {
        var mediaNode = this.audioContext.createMediaElementSource(audio);
        mediaNode.connect(this.gainNode);

        this.mediaNode = mediaNode;
    };

    WebGLSoundSource.create = function (sd, id, params) {
        var source = new WebGLSoundSource();

        source.sd = sd;
        source.id = id;

        source.sound = null;
        source.audio = null;
        source.playing = false;
        source.paused = false;

        var buffer = new Float32Array(9);
        source._position = buffer.subarray(0, 3);
        source._velocity = buffer.subarray(3, 6);
        source._direction = buffer.subarray(6, 9);

        var gain = (typeof params.gain === "number" ? params.gain : 1);
        var looping = (params.looping || false);
        var pitch = (params.pitch || 1);

        var audioContext = sd.audioContext;
        if (audioContext) {
            source.bufferNode = null;
            source.mediaNode = null;
            source.playStart = -1;
            source.playPaused = -1;

            var masterGainNode = sd.gainNode;

            var pannerNode = audioContext.createPanner();
            source.pannerNode = pannerNode;
            pannerNode.connect(masterGainNode);

            var gainNode = (audioContext.createGain ? audioContext.createGain() : audioContext.createGainNode());
            gainNode.gain.value = gain;
            source.gainNode = gainNode;
            gainNode.connect(pannerNode);

            if (sd.linearDistance) {
                if (typeof pannerNode.distanceModel === "string") {
                    pannerNode.distanceModel = "linear";
                } else if (typeof pannerNode.LINEAR_DISTANCE === "number") {
                    pannerNode.distanceModel = pannerNode.LINEAR_DISTANCE;
                }
            }

            if (typeof pannerNode.panningModel === "string") {
                pannerNode.panningModel = "equalpower";
            } else {
                pannerNode.panningModel = pannerNode.EQUALPOWER;
            }

            source.updateRelativePosition = source.updateRelativePositionWebAudio;

            Object.defineProperty(source, "position", {
                get: function getPositionFn() {
                    return this._position.slice();
                },
                set: function setPositionFn(newPosition) {
                    var oldPosition = this._position;
                    if (oldPosition[0] !== newPosition[0] || oldPosition[1] !== newPosition[1] || oldPosition[2] !== newPosition[2]) {
                        oldPosition[0] = newPosition[0];
                        oldPosition[1] = newPosition[1];
                        oldPosition[2] = newPosition[2];
                        if (!this.relative) {
                            this.pannerNode.setPosition(newPosition[0], newPosition[1], newPosition[2]);
                        }
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "direction", {
                get: function getDirectionFn() {
                    return this._direction.slice();
                },
                set: function setDirectionFn(newDirection) {
                    this._direction = VMath.v3Copy(newDirection, this._direction);
                    this.pannerNode.setOrientation(newDirection[0], newDirection[1], newDirection[2]);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "velocity", {
                get: function getVelocityFn() {
                    return this._velocity.slice();
                },
                set: function setVelocityFn(newVelocity) {
                    this._velocity = VMath.v3Copy(newVelocity, this._velocity);
                    this.pannerNode.setVelocity(newVelocity[0], newVelocity[1], newVelocity[2]);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "gain", {
                get: function getGainFn() {
                    return gain;
                },
                set: function setGainFn(newGain) {
                    if (gain !== newGain) {
                        gain = newGain;
                        this.gainNode.gain.value = newGain;
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "looping", {
                get: function getLoopingFn() {
                    return looping;
                },
                set: function setLoopingFn(newLooping) {
                    looping = newLooping;
                    var audio = this.audio;
                    if (audio) {
                        audio.loop = newLooping;
                    } else {
                        var bufferNode = this.bufferNode;
                        if (bufferNode) {
                            bufferNode.loop = newLooping;
                        }
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "pitch", {
                get: function getPitchFn() {
                    return pitch;
                },
                set: function setPitchFn(newPitch) {
                    pitch = newPitch;
                    var audio = this.audio;
                    if (audio) {
                        audio.playbackRate = newPitch;
                    } else {
                        var bufferNode = this.bufferNode;
                        if (bufferNode) {
                            if (bufferNode.playbackRate) {
                                bufferNode.playbackRate.value = newPitch;
                            }
                        }
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "tell", {
                get: function tellFn() {
                    if (this.playing) {
                        var audio = this.audio;
                        if (audio) {
                            return audio.currentTime;
                        } else {
                            if (this.paused) {
                                return (this.playPaused - this.playStart);
                            } else {
                                return (audioContext.currentTime - this.playStart);
                            }
                        }
                    } else {
                        return 0;
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "minDistance", {
                get: function getMinDistanceFn() {
                    return pannerNode.refDistance;
                },
                set: function setMinDistanceFn(minDistance) {
                    if (this.pannerNode.maxDistance === minDistance) {
                        minDistance = this.pannerNode.maxDistance * 0.999;
                    }
                    this.pannerNode.refDistance = minDistance;
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "maxDistance", {
                get: function getMaxDistanceFn() {
                    return pannerNode.maxDistance;
                },
                set: function setMaxDistanceFn(maxDistance) {
                    if (this.pannerNode.refDistance === maxDistance) {
                        maxDistance = this.pannerNode.refDistance * 1.001;
                    }
                    this.pannerNode.maxDistance = maxDistance;
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "rollOff", {
                get: function getRolloffFactorFn() {
                    return pannerNode.rolloffFactor;
                },
                set: function setRolloffFactorFn(rollOff) {
                    this.pannerNode.rolloffFactor = rollOff;
                },
                enumerable: true,
                configurable: false
            });

            source.loopAudio = function loopAudioFn() {
                source.stop();
            };
        } else {
            source.gainFactor = 1;

            source.updateAudioVolume = function updateAudioVolumeFn() {
                var audio = this.audio;
                if (audio) {
                    var volume = Math.min((this.gainFactor * gain), 1);
                    audio.volume = volume;
                    if (0 >= volume) {
                        audio.muted = true;
                    } else {
                        audio.muted = false;
                    }
                }
            };

            source.updateRelativePosition = source.updateRelativePositionHTML5;

            Object.defineProperty(source, "position", {
                get: function getPositionFn() {
                    return this._position.slice();
                },
                set: function setPositionFn(newPosition) {
                    this._position = VMath.v3Copy(newPosition, this._position);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "direction", {
                get: function getDirectionFn() {
                    return this._direction.slice();
                },
                set: function setDirectionFn(newDirection) {
                    this._direction = VMath.v3Copy(newDirection, this._direction);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "velocity", {
                get: function getVelocityFn() {
                    return this._velocity.slice();
                },
                set: function setVelocityFn(newVelocity) {
                    this._velocity = VMath.v3Copy(newVelocity, this._velocity);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "gain", {
                get: function getGainFn() {
                    return gain;
                },
                set: function setGainFn(newGain) {
                    gain = newGain;
                    this.updateAudioVolume();
                },
                enumerable: true,
                configurable: false
            });

            if (sd.loopingSupported) {
                Object.defineProperty(source, "looping", {
                    get: function getLoopingFn() {
                        return looping;
                    },
                    set: function setLoopingFn(newLooping) {
                        looping = newLooping;
                        var audio = this.audio;
                        if (audio) {
                            audio.loop = newLooping;
                        }
                    },
                    enumerable: true,
                    configurable: false
                });

                source.loopAudio = function loopAudioFn() {
                    source.stop();
                };
            } else {
                source.looping = looping;

                source.loopAudio = function loopAudioFn() {
                    var audio = source.audio;
                    if (audio) {
                        if (this.looping) {
                            audio.currentTime = 0;
                            audio.play();
                        } else {
                            source.stop();
                        }
                    }
                };
            }

            Object.defineProperty(source, "pitch", {
                get: function getPitchFn() {
                    return pitch;
                },
                set: function setPitchFn(newPitch) {
                    pitch = newPitch;
                    var audio = this.audio;
                    if (audio) {
                        audio.playbackRate = newPitch;
                    }
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(source, "tell", {
                get: function tellFn() {
                    if (this.playing) {
                        var audio = this.audio;
                        if (audio) {
                            return audio.currentTime;
                        }
                    }
                    return 0;
                },
                enumerable: true,
                configurable: false
            });
        }

        source.relative = (params.relative || false);
        source.minDistance = (params.minDistance || 1);
        source.maxDistance = (params.maxDistance || 3.402823466e+38);
        source.rollOff = (params.rollOff || 1);

        if (params.position) {
            source.position = params.position;
        }
        if (params.velocity) {
            source.velocity = params.velocity;
        }
        if (params.direction) {
            source.direction = params.direction;
        }

        return source;
    };
    WebGLSoundSource.version = 1;
    return WebGLSoundSource;
})();

//
// WebGLSoundDevice
//
var WebGLSoundDevice = (function () {
    function WebGLSoundDevice() {
    }
    // Public API
    WebGLSoundDevice.prototype.createSource = function (params) {
        this.lastSourceID += 1;
        return WebGLSoundSource.create(this, this.lastSourceID, params);
    };

    WebGLSoundDevice.prototype.createSound = function (params) {
        return WebGLSound.create(this, params);
    };

    WebGLSoundDevice.prototype.loadSoundsArchive = function (params) {
        var src = params.src;
        if (typeof SoundTARLoader !== 'undefined') {
            SoundTARLoader.create({
                sd: this,
                src: src,
                uncompress: params.uncompress,
                onsoundload: function tarSoundLoadedFn(texture) {
                    params.onsoundload(texture);
                },
                onload: function soundTarLoadedFn(success, status) {
                    if (params.onload) {
                        params.onload(success, status);
                    }
                },
                onerror: function soundTarFailedFn(status) {
                    if (params.onload) {
                        params.onload(false, status);
                    }
                }
            });
            return true;
        } else {
            (TurbulenzEngine).callOnError('Missing archive loader required for ' + src);
            return false;
        }
    };

    WebGLSoundDevice.prototype.createEffect = function (params) {
        return null;
    };

    WebGLSoundDevice.prototype.createEffectSlot = function (params) {
        return null;
    };

    WebGLSoundDevice.prototype.createFilter = function (params) {
        return null;
    };

    WebGLSoundDevice.prototype.update = function () {
        var listenerTransform = this.listenerTransform;
        var listenerPosition0 = listenerTransform[9];
        var listenerPosition1 = listenerTransform[10];
        var listenerPosition2 = listenerTransform[11];

        var numPlayingSources = this.numPlayingSources;
        var playingSources = this.playingSources;
        var n;
        for (n = 0; n < numPlayingSources; n += 1) {
            playingSources[n].updateRelativePosition(listenerPosition0, listenerPosition1, listenerPosition2);
        }
    };

    WebGLSoundDevice.prototype.isSupported = function (name) {
        if ("FILEFORMAT_OGG" === name) {
            return this.supportedExtensions.ogg;
        } else if ("FILEFORMAT_MP3" === name) {
            return this.supportedExtensions.mp3;
        } else if ("FILEFORMAT_WAV" === name) {
            return this.supportedExtensions.wav;
        }
        return false;
    };

    // Private API
    WebGLSoundDevice.prototype.addLoadingSound = function (soundCheckCall) {
        var loadingSounds = this.loadingSounds;
        loadingSounds[loadingSounds.length] = soundCheckCall;

        var loadingInterval = this.loadingInterval;
        var that = this;
        if (loadingInterval === null) {
            this.loadingInterval = loadingInterval = window.setInterval(function checkLoadingSources() {
                var numLoadingSounds = loadingSounds.length;
                var n = 0;
                do {
                    var soundCheck = loadingSounds[n];
                    if (soundCheck()) {
                        numLoadingSounds -= 1;
                        if (n < numLoadingSounds) {
                            loadingSounds[n] = loadingSounds[numLoadingSounds];
                        }
                        loadingSounds.length = numLoadingSounds;
                    } else {
                        n += 1;
                    }
                } while(n < numLoadingSounds);
                if (numLoadingSounds === 0) {
                    window.clearInterval(loadingInterval);
                    that.loadingInterval = null;
                }
            }, 100);
        }
    };

    WebGLSoundDevice.prototype.addPlayingSource = function (source) {
        var id = source.id;
        if (!this.playingSourcesMap[id]) {
            this.playingSourcesMap[id] = true;
            var numPlayingSources = this.numPlayingSources;
            this.playingSources[numPlayingSources] = source;
            this.numPlayingSources = (numPlayingSources + 1);
        }
    };

    WebGLSoundDevice.prototype.removePlayingSource = function (source) {
        delete this.playingSourcesMap[source.id];

        var numPlayingSources = this.numPlayingSources;
        var playingSources = this.playingSources;
        var n;
        for (n = 0; n < numPlayingSources; n += 1) {
            if (playingSources[n] === source) {
                numPlayingSources -= 1;
                playingSources[n] = playingSources[numPlayingSources];
                playingSources[numPlayingSources] = null;
                this.numPlayingSources = numPlayingSources;
                break;
            }
        }
    };

    WebGLSoundDevice.prototype.isResourceSupported = function (soundPath) {
        var extension = soundPath.slice(-3).toLowerCase();
        return this.supportedExtensions[extension];
    };

    WebGLSoundDevice.prototype.destroy = function () {
        var loadingInterval = this.loadingInterval;
        if (loadingInterval !== null) {
            window.clearInterval(loadingInterval);
            this.loadingInterval = null;
        }

        var loadingSounds = this.loadingSounds;
        if (loadingSounds) {
            loadingSounds.length = 0;
            this.loadingSounds = null;
        }

        var numPlayingSources = this.numPlayingSources;
        var playingSources = this.playingSources;
        var n;
        for (n = 0; n < numPlayingSources; n += 1) {
            playingSources[n]._stop();
        }

        this.numPlayingSources = 0;
        this.playingSources = null;
        this.playingSourcesMap = null;

        WebGLSound.prototype.audioContext = null;
        WebGLSoundSource.prototype.audioContext = null;
    };

    WebGLSoundDevice.create = function (params) {
        var sd = new WebGLSoundDevice();

        sd.extensions = '';
        sd.renderer = 'HTML5 Audio';
        sd.alcVersion = "0";
        sd.alcExtensions = '';
        sd.alcEfxVersion = "0";
        sd.alcMaxAuxiliarySends = 0;

        sd.deviceSpecifier = (params.deviceSpecifier || null);
        sd.frequency = (params.frequency || 44100);
        sd.dopplerFactor = (params.dopplerFactor || 1);
        sd.dopplerVelocity = (params.dopplerVelocity || 1);
        sd.speedOfSound = (params.speedOfSound || 343.29998779296875);
        sd.linearDistance = (params.linearDistance !== undefined ? params.linearDistance : true);

        sd.loadingSounds = [];
        sd.loadingInterval = null;

        sd.numPlayingSources = 0;
        sd.playingSources = [];
        sd.playingSourcesMap = {};

        sd.lastSourceID = 0;

        var AudioContextConstructor;

        if (sd.deviceSpecifier !== "audioelement") {
            AudioContextConstructor = (window.AudioContext || window.webkitAudioContext);
        }

        if (AudioContextConstructor) {
            var audioContext;
            try  {
                audioContext = new AudioContextConstructor();
            } catch (error) {
                (TurbulenzEngine).callOnError('Failed to create AudioContext:' + error);
                return null;
            }

            if (audioContext.sampleRate === 0) {
                return null;
            }

            // HTML5 + WebAudio just does not work on Android or iOS
            // and it seems to crash Chrome and perform poorly on Firefox...
            //WebGLSound.prototype.forceUncompress = (TurbulenzEngine.getSystemInfo().platformProfile !== 'desktop' ||
            //                                        !audioContext.createMediaElementSource);
            WebGLSound.prototype.forceUncompress = true;

            WebGLSound.prototype.audioContext = audioContext;
            WebGLSoundSource.prototype.audioContext = audioContext;

            sd.renderer = 'WebAudio';
            sd.audioContext = audioContext;
            sd.frequency = audioContext.sampleRate;

            sd.gainNode = (audioContext.createGain ? audioContext.createGain() : audioContext.createGainNode());
            sd.gainNode.connect(audioContext.destination);

            var listener = audioContext.listener;
            listener.dopplerFactor = sd.dopplerFactor;
            listener.speedOfSound = sd.speedOfSound;

            var listenerTransform, listenerVelocity;

            Object.defineProperty(sd, "listenerTransform", {
                get: function getListenerTransformFn() {
                    return listenerTransform.slice();
                },
                set: function setListenerTransformFn(transform) {
                    listenerTransform = VMath.m43Copy(transform, listenerTransform);

                    var position0 = transform[9];
                    var position1 = transform[10];
                    var position2 = transform[11];

                    listener.setPosition(position0, position1, position2);

                    listener.setOrientation(-transform[6], -transform[7], -transform[8], transform[3], transform[4], transform[5]);
                },
                enumerable: true,
                configurable: false
            });

            Object.defineProperty(sd, "listenerVelocity", {
                get: function getListenerVelocityFn() {
                    return listenerVelocity.slice();
                },
                set: function setListenerVelocityFn(velocity) {
                    listenerVelocity = VMath.v3Copy(velocity, listenerVelocity);
                    listener.setVelocity(velocity[0], velocity[1], velocity[2]);
                },
                enumerable: true,
                configurable: false
            });

            sd.update = function soundDeviceUpdate() {
                this.gainNode.gain.value = this.listenerGain;

                var listenerPosition0 = listenerTransform[9];
                var listenerPosition1 = listenerTransform[10];
                var listenerPosition2 = listenerTransform[11];

                var numPlayingSources = this.numPlayingSources;
                var playingSources = this.playingSources;
                var playingSourcesMap = this.playingSourcesMap;

                var currentTime = audioContext.currentTime;

                var n = 0;
                while (n < numPlayingSources) {
                    var source = playingSources[n];

                    var bufferNode = source.bufferNode;
                    if (bufferNode) {
                        var tell = (currentTime - source.playStart);
                        var duration = bufferNode.buffer.duration;
                        if (duration < tell) {
                            if (source.looping) {
                                source.playStart = (currentTime - (tell - duration));
                            } else {
                                bufferNode.disconnect();
                                source.playing = false;
                                source.sound = null;
                                source.bufferNode = null;

                                numPlayingSources -= 1;
                                playingSources[n] = playingSources[numPlayingSources];
                                playingSources[numPlayingSources] = null;
                                delete playingSourcesMap[source.id];

                                continue;
                            }
                        }
                    }

                    if (source.relative) {
                        source.updateRelativePosition(listenerPosition0, listenerPosition1, listenerPosition2);
                    }

                    n += 1;
                }

                this.numPlayingSources = numPlayingSources;
                if (numPlayingSources < (playingSources.length >> 1)) {
                    playingSources.length = numPlayingSources;
                }
            };
        } else {
            WebGLSound.prototype.forceUncompress = false;
        }

        sd.listenerTransform = (params.listenerTransform || VMath.m43BuildIdentity());
        sd.listenerVelocity = (params.listenerVelocity || VMath.v3BuildZero());
        sd.listenerGain = (typeof params.listenerGain === "number" ? params.listenerGain : 1);

        // Need a temporary Audio element to test capabilities
        var audio;
        try  {
            audio = new Audio();
        } catch (error) {
            (TurbulenzEngine).callOnError('Failed to create Audio:' + error);
            return null;
        }

        if (sd.audioContext) {
            sd.loopingSupported = true;
        } else {
            if (audio.mozSetup) {
                try  {
                    audio.mozSetup(1, 22050);
                } catch (e) {
                    return null;
                }
            }

            // Check for looping support
            sd.loopingSupported = (typeof audio.loop === 'boolean');
        }

        // Check for supported extensions
        var supportedExtensions = {
            ogg: false,
            mp3: false,
            wav: false
        };
        if (audio.canPlayType('application/ogg')) {
            supportedExtensions.ogg = true;
        }
        if (audio.canPlayType('audio/mp3')) {
            supportedExtensions.mp3 = true;
        }
        if (audio.canPlayType('audio/wav')) {
            supportedExtensions.wav = true;
        }
        sd.supportedExtensions = supportedExtensions;

        audio = null;

        return sd;
    };
    WebGLSoundDevice.version = 1;
    return WebGLSoundDevice;
})();

WebGLSoundDevice.prototype.vendor = "Turbulenz";

// Copyright (c) 2011-2012 Turbulenz Limited
/*global TurbulenzEngine*/
/*global Uint8Array*/
/*global window*/

if ((typeof ArrayBuffer !== "undefined") && (ArrayBuffer.prototype !== undefined) && (ArrayBuffer.prototype.slice === undefined)) {
    ArrayBuffer.prototype.slice = function ArrayBufferSlice(s, e) {
        var length = this.byteLength;
        if (s === undefined) {
            s = 0;
        } else if (s < 0) {
            s += length;
        }
        if (e === undefined) {
            e = length;
        } else if (e < 0) {
            e += length;
        }

        length = (e - s);
        if (0 < length) {
            var src = new Uint8Array(this, s, length);
            var dst = new Uint8Array(src);
            return dst.buffer;
        } else {
            return new ArrayBuffer(0);
        }
    };
}

//
// SoundTARLoader
//
var SoundTARLoader = (function () {
    function SoundTARLoader() {
    }
    SoundTARLoader.prototype.processBytes = function (bytes) {
        var offset = 0;
        var totalSize = bytes.length;

        function skip(limit) {
            offset += limit;
        }

        function getString(limit) {
            var index = offset;
            var nextOffset = (index + limit);
            var c = bytes[index];
            var ret;
            if (c && 0 < limit) {
                index += 1;
                var s = new Array(limit);
                var n = 0;
                do {
                    s[n] = c;
                    n += 1;

                    c = bytes[index];
                    index += 1;
                } while(c && n < limit);

                while (s[n - 1] === 32) {
                    n -= 1;
                }
                s.length = n;
                ret = String.fromCharCode.apply(null, s);
            } else {
                ret = '';
            }
            offset = nextOffset;
            return ret;
        }

        function getNumber(text) {
            /*jshint regexp: false*/
            text = text.replace(/[^\d]/g, '');

            /*jshint regexp: true*/
            return parseInt('0' + text, 8);
        }

        var header = {
            fileName: null,
            //mode : null,
            //uid : null,
            //gid : null,
            length: 0,
            //lastModified : null,
            //checkSum : null,
            fileType: null,
            //linkName : null,
            ustarSignature: null,
            //ustarVersion : null,
            //ownerUserName : null,
            //ownerGroupName : null,
            //deviceMajor : null,
            //deviceMinor : null,
            fileNamePrefix: null
        };

        function parseHeader(header) {
            header.fileName = getString(100);
            skip(8);
            skip(8);
            skip(8);
            header.length = getNumber(getString(12));
            skip(12);
            skip(8);
            header.fileType = getString(1);
            skip(100);
            header.ustarSignature = getString(6);
            skip(2);
            skip(32);
            skip(32);
            skip(8);
            skip(8);
            header.fileNamePrefix = getString(155);
            offset += 12;
        }

        var sd = this.sd;
        var uncompress = this.uncompress;
        var onsoundload = this.onsoundload;
        var result = true;

        // This function is called for each sound in the archive,
        // synchronously if there is an immediate error,
        // asynchronously otherwise.  If one fails, the load result
        // for the whole archive is false.
        this.soundsLoading = 0;
        var that = this;
        function onload(sound) {
            that.soundsLoading -= 1;
            if (sound) {
                onsoundload(sound);
            } else {
                result = false;
            }
        }

        while ((offset + 512) <= totalSize) {
            parseHeader(header);
            if (0 < header.length) {
                var fileName;
                if (header.fileName === "././@LongLink") {
                    // name in next chunk
                    fileName = getString(256);
                    offset += 256;

                    parseHeader(header);
                } else {
                    if (header.fileNamePrefix && header.ustarSignature === "ustar") {
                        fileName = (header.fileNamePrefix + header.fileName);
                    } else {
                        fileName = header.fileName;
                    }
                }
                if ('' === header.fileType || '0' === header.fileType) {
                    //console.log('Loading "' + fileName + '" (' + header.length + ')');
                    this.soundsLoading += 1;
                    sd.createSound({
                        src: fileName,
                        data: (sd.audioContext ? bytes.buffer.slice(offset, (offset + header.length)) : bytes.subarray(offset, (offset + header.length))),
                        uncompress: uncompress,
                        onload: onload
                    });
                }
                offset += (Math.floor((header.length + 511) / 512) * 512);
            }
        }

        bytes = null;

        return result;
    };

    SoundTARLoader.prototype.isValidHeader = function (header) {
        return true;
    };

    SoundTARLoader.create = function (params) {
        var loader = new SoundTARLoader();
        loader.sd = params.sd;
        loader.uncompress = params.uncompress;
        loader.onsoundload = params.onsoundload;
        loader.onload = params.onload;
        loader.onerror = params.onerror;
        loader.soundsLoading = 0;

        var src = params.src;
        if (src) {
            loader.src = src;
            var xhr;
            if (window.XMLHttpRequest) {
                xhr = new window.XMLHttpRequest();
            } else if (window.ActiveXObject) {
                xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
            } else {
                if (params.onerror) {
                    params.onerror(0);
                }
                return null;
            }

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                        var xhrStatus = xhr.status;
                        var xhrStatusText = xhr.status !== 0 && xhr.statusText || 'No connection';

                        if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                            xhrStatus = 200;
                        }

                        if (xhr.getAllResponseHeaders() === "") {
                            var noBody;
                            if (xhr.responseType === "arraybuffer") {
                                noBody = !xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                noBody = !xhr.mozResponseArrayBuffer;
                            } else {
                                noBody = !xhr.responseText;
                            }
                            if (noBody) {
                                if (loader.onerror) {
                                    loader.onerror(0);
                                }

                                // break circular reference
                                xhr.onreadystatechange = null;
                                xhr = null;
                                return;
                            }
                        }

                        if (xhrStatus === 200 || xhrStatus === 0) {
                            var buffer;
                            if (xhr.responseType === "arraybuffer") {
                                buffer = xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                buffer = xhr.mozResponseArrayBuffer;
                            } else {
                                /*jshint bitwise: false*/
                                var text = xhr.responseText;
                                var numChars = text.length;
                                var i;
                                buffer = [];
                                buffer.length = numChars;
                                for (i = 0; i < numChars; i += 1) {
                                    buffer[i] = (text.charCodeAt(i) & 0xff);
                                }
                                /*jshint bitwise: true*/
                            }

                            // processBytes returns false if any of the
                            // entries in the archive was not supported or
                            // couldn't be loaded as a sound.
                            var archiveResult = loader.processBytes(new Uint8Array(buffer));

                            if (loader.onload) {
                                var callOnload = function callOnloadFn() {
                                    if (0 < loader.soundsLoading) {
                                        if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                                            window.setTimeout(callOnload, 100);
                                        }
                                    } else {
                                        loader.onload(archiveResult, xhrStatus);
                                    }
                                };
                                callOnload();
                            }
                        } else {
                            if (loader.onerror) {
                                loader.onerror(xhrStatus);
                            }
                        }
                    }

                    // break circular reference
                    xhr.onreadystatechange = null;
                    xhr = null;
                }
            };
            xhr.open("GET", params.src, true);
            if (typeof xhr.responseType === "string" || (xhr.hasOwnProperty && xhr.hasOwnProperty("responseType"))) {
                xhr.responseType = "arraybuffer";
            } else if (xhr.overrideMimeType) {
                xhr.overrideMimeType("text/plain; charset=x-user-defined");
            } else {
                xhr.setRequestHeader("Content-Type", "text/plain; charset=x-user-defined");
            }
            xhr.send(null);
        }

        return loader;
    };
    SoundTARLoader.version = 1;
    return SoundTARLoader;
})();

// Copyright (c) 2011-2012 Turbulenz Limited
/*global TurbulenzEngine*/
/*global Uint8Array*/
/*global window*/

;

var TARLoader = (function () {
    function TARLoader() {
    }
    TARLoader.prototype.processBytes = function (bytes) {
        var offset = 0;
        var totalSize = bytes.length;

        function skip(limit) {
            offset += limit;
        }

        function getString(limit) {
            var index = offset;
            var nextOffset = (index + limit);
            var c = bytes[index];
            var ret;
            if (c && 0 < limit) {
                index += 1;
                var s = new Array(limit);
                var n = 0;
                do {
                    s[n] = c;
                    n += 1;

                    c = bytes[index];
                    index += 1;
                } while(c && n < limit);

                while (s[n - 1] === 32) {
                    n -= 1;
                }
                s.length = n;
                ret = String.fromCharCode.apply(null, s);
            } else {
                ret = '';
            }
            offset = nextOffset;
            return ret;
        }

        function getNumber(text) {
            /*jshint regexp: false*/
            text = text.replace(/[^\d]/g, '');

            /*jshint regexp: true*/
            return parseInt('0' + text, 8);
        }

        var header = {
            fileName: null,
            //mode : null,
            //uid : null,
            //gid : null,
            length: 0,
            //lastModified : null,
            //checkSum : null,
            fileType: null,
            //linkName : null,
            ustarSignature: null,
            //ustarVersion : null,
            //ownerUserName : null,
            //ownerGroupName : null,
            //deviceMajor : null,
            //deviceMinor : null,
            fileNamePrefix: null
        };

        function parseHeader(header) {
            header.fileName = getString(100);
            skip(8);
            skip(8);
            skip(8);
            header.length = getNumber(getString(12));
            skip(12);
            skip(8);
            header.fileType = getString(1);
            skip(100);
            header.ustarSignature = getString(6);
            skip(2);
            skip(32);
            skip(32);
            skip(8);
            skip(8);
            header.fileNamePrefix = getString(155);
            offset += 12;
        }

        var gd = this.gd;
        var mipmaps = this.mipmaps;
        var ontextureload = this.ontextureload;
        var result = true;

        this.texturesLoading = 0;
        var that = this;
        function onload(texture) {
            that.texturesLoading -= 1;
            if (texture) {
                ontextureload(texture);
            } else {
                offset = totalSize;
                result = false;
            }
        }

        while ((offset + 512) <= totalSize) {
            parseHeader(header);
            if (0 < header.length) {
                var fileName;
                if (header.fileName === "././@LongLink") {
                    // name in next chunk
                    fileName = getString(256);
                    offset += 256;

                    parseHeader(header);
                } else {
                    if (header.fileNamePrefix && header.ustarSignature === "ustar") {
                        fileName = (header.fileNamePrefix + header.fileName);
                    } else {
                        fileName = header.fileName;
                    }
                }
                if ('' === header.fileType || '0' === header.fileType) {
                    //console.log('Loading "' + fileName + '" (' + header.length + ')');
                    this.texturesLoading += 1;
                    gd.createTexture({
                        src: fileName,
                        data: bytes.subarray(offset, (offset + header.length)),
                        mipmaps: mipmaps,
                        onload: onload
                    });
                }
                offset += (Math.floor((header.length + 511) / 512) * 512);
            }
        }

        bytes = null;

        return result;
    };

    TARLoader.prototype.isValidHeader = function (/* header */ ) {
        return true;
    };

    TARLoader.create = function (params) {
        var loader = new TARLoader();
        loader.gd = params.gd;
        loader.mipmaps = params.mipmaps;
        loader.ontextureload = params.ontextureload;
        loader.onload = params.onload;
        loader.onerror = params.onerror;
        loader.texturesLoading = 0;

        var src = params.src;
        if (src) {
            loader.src = src;
            var xhr;
            if (window.XMLHttpRequest) {
                xhr = new window.XMLHttpRequest();
            } else if (window.ActiveXObject) {
                xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
            } else {
                if (params.onerror) {
                    params.onerror(0);
                }
                return null;
            }

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                        var xhrStatus = xhr.status;
                        var xhrStatusText = xhr.status !== 0 && xhr.statusText || 'No connection';

                        if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                            xhrStatus = 200;
                        }

                        if (xhr.getAllResponseHeaders() === "") {
                            var noBody;
                            if (xhr.responseType === "arraybuffer") {
                                noBody = !xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                noBody = !xhr.mozResponseArrayBuffer;
                            } else {
                                noBody = !xhr.responseText;
                            }
                            if (noBody) {
                                if (loader.onerror) {
                                    loader.onerror(0);
                                }

                                // break circular reference
                                xhr.onreadystatechange = null;
                                xhr = null;
                                return;
                            }
                        }

                        if (xhrStatus === 200 || xhrStatus === 0) {
                            var buffer;
                            if (xhr.responseType === "arraybuffer") {
                                buffer = xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                buffer = xhr.mozResponseArrayBuffer;
                            } else {
                                /*jshint bitwise: false*/
                                var text = xhr.responseText;
                                var numChars = text.length;
                                buffer = [];
                                buffer.length = numChars;
                                for (var i = 0; i < numChars; i += 1) {
                                    buffer[i] = (text.charCodeAt(i) & 0xff);
                                }
                                /*jshint bitwise: true*/
                            }

                            if (loader.processBytes(new Uint8Array(buffer))) {
                                if (loader.onload) {
                                    var callOnload = function callOnloadFn() {
                                        if (0 < loader.texturesLoading) {
                                            if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                                                window.setTimeout(callOnload, 100);
                                            }
                                        } else {
                                            loader.onload(true, xhrStatus);
                                        }
                                    };
                                    callOnload();
                                }
                            } else {
                                if (loader.onerror) {
                                    loader.onerror(xhrStatus);
                                }
                            }
                        } else {
                            if (loader.onerror) {
                                loader.onerror(xhrStatus);
                            }
                        }
                    }

                    // break circular reference
                    xhr.onreadystatechange = null;
                    xhr = null;
                }
            };
            xhr.open("GET", params.src, true);
            if (typeof xhr.responseType === "string" || (xhr.hasOwnProperty && xhr.hasOwnProperty("responseType"))) {
                xhr.responseType = "arraybuffer";
            } else if (xhr.overrideMimeType) {
                xhr.overrideMimeType("text/plain; charset=x-user-defined");
            } else {
                xhr.setRequestHeader("Content-Type", "text/plain; charset=x-user-defined");
            }
            xhr.send(null);
        }

        return loader;
    };
    TARLoader.version = 1;
    return TARLoader;
})();

// Copyright (c) 2011-2012 Turbulenz Limited
/*global TurbulenzEngine*/
/*global Uint8Array*/
/*global Uint16Array*/
/*global window*/

//
// TGALoader
//
var TGALoader = (function () {
    function TGALoader() {
    }
    TGALoader.prototype.processBytes = function (bytes) {
        var header = this.parseHeader(bytes);
        if (!this.isValidHeader(header)) {
            return;
        }

        var offset = 18;

        this.width = header.width;
        this.height = header.height;

        this.bytesPerPixel = Math.floor(header.bpp / 8);

        /*jshint bitwise: false*/
        this.horzRev = (header.descriptor & this.DESC_HORIZONTAL);
        this.vertRev = !(header.descriptor & this.DESC_VERTICAL);

        /*jshint bitwise: true*/
        var rle = false;

        var gd = this.gd;
        switch (header.imageType) {
            case this.TYPE_MAPPED_RLE:
                rle = true;
                if (header.colorMapSize > 24) {
                    this.format = gd.PIXELFORMAT_R8G8B8A8;
                } else if (header.colorMapSize > 16) {
                    this.format = gd.PIXELFORMAT_R8G8B8;
                } else {
                    this.format = gd.PIXELFORMAT_R5G5B5A1;
                }
                break;

            case this.TYPE_MAPPED:
                if (header.colorMapSize > 24) {
                    this.format = gd.PIXELFORMAT_R8G8B8A8;
                } else if (header.colorMapSize > 16) {
                    this.format = gd.PIXELFORMAT_R8G8B8;
                } else {
                    this.format = gd.PIXELFORMAT_R5G5B5A1;
                }
                break;

            case this.TYPE_GRAY_RLE:
                rle = true;
                this.format = gd.PIXELFORMAT_L8;
                break;

            case this.TYPE_GRAY:
                this.format = gd.PIXELFORMAT_L8;
                break;

            case this.TYPE_COLOR_RLE:
                rle = true;
                switch (this.bytesPerPixel) {
                    case 4:
                        this.format = gd.PIXELFORMAT_R8G8B8A8;
                        break;

                    case 3:
                        this.format = gd.PIXELFORMAT_R8G8B8;
                        break;

                    case 2:
                        this.format = gd.PIXELFORMAT_R5G5B5A1;
                        break;

                    default:
                        return;
                }
                break;

            case this.TYPE_COLOR:
                switch (this.bytesPerPixel) {
                    case 4:
                        this.format = gd.PIXELFORMAT_R8G8B8A8;
                        break;

                    case 3:
                        this.format = gd.PIXELFORMAT_R8G8B8;
                        break;

                    case 2:
                        this.format = gd.PIXELFORMAT_R5G5B5A1;
                        break;

                    default:
                        return;
                }
                break;

            default:
                return;
        }

        if (header.idLength) {
            offset += header.idLength;
            if (offset > bytes.length) {
                return;
            }
        }

        if (this.TYPE_MAPPED_RLE === header.imageType || this.TYPE_MAPPED === header.imageType) {
            if (header.colorMapType !== 1) {
                return;
            }
        } else if (header.colorMapType !== 0) {
            return;
        }

        if (header.colorMapType === 1) {
            var index = header.colorMapIndex;
            var length = header.colorMapLength;

            if (length === 0) {
                return;
            }

            var pelbytes = Math.floor(header.colorMapSize / 8);
            var numColors = (length + index);
            var colorMap = [];
            colorMap.length = (numColors * pelbytes);

            this.colorMap = colorMap;
            this.colorMapBytesPerPixel = pelbytes;

            // Zero the entries up to the beginning of the map
            var j;
            for (j = 0; j < (index * pelbytes); j += 1) {
                colorMap[j] = 0;
            }

            for (j = (index * pelbytes); j < (index * pelbytes); j += 1, offset += 1) {
                colorMap[j] = bytes[offset];
            }

            offset += (length * pelbytes);
            if (offset > bytes.length) {
                return;
            }

            if (pelbytes >= 3) {
                for (j = (index * pelbytes); j < (length * pelbytes); j += pelbytes) {
                    var tmp = colorMap[j];
                    colorMap[j] = colorMap[j + 2];
                    colorMap[j + 2] = tmp;
                }
            }
        }

        var data = bytes.subarray(offset);
        bytes = null;

        if (rle) {
            data = this.expandRLE(data);
        }

        var size = (this.width * this.height * this.bytesPerPixel);
        if (data.length < size) {
            return;
        }

        if (this.horzRev) {
            this.flipHorz(data);
        }

        if (this.vertRev) {
            this.flipVert(data);
        }

        if (this.colorMap) {
            data = this.expandColorMap(data);
        } else if (2 < this.bytesPerPixel) {
            this.convertBGR2RGB(data);
        } else if (2 === this.bytesPerPixel) {
            data = this.convertARGB2RGBA(data);
        }

        this.data = data;
    };

    TGALoader.prototype.parseHeader = function (bytes) {
        /*jshint bitwise: false*/
        var header = {
            idLength: bytes[0],
            colorMapType: bytes[1],
            imageType: bytes[2],
            colorMapIndex: ((bytes[4] << 8) | bytes[3]),
            colorMapLength: ((bytes[6] << 8) | bytes[5]),
            colorMapSize: bytes[7],
            xOrigin: ((bytes[9] << 8) | bytes[8]),
            yOrigin: ((bytes[11] << 8) | bytes[10]),
            width: ((bytes[13] << 8) | bytes[12]),
            height: ((bytes[15] << 8) | bytes[14]),
            bpp: bytes[16],
            // Image descriptor:
            // 3-0: attribute bpp
            // 4:   left-to-right
            // 5:   top-to-bottom
            // 7-6: zero
            descriptor: bytes[17]
        };

        /*jshint bitwise: true*/
        return header;
    };

    TGALoader.prototype.isValidHeader = function (header) {
        if (this.TYPE_MAPPED_RLE === header.imageType || this.TYPE_MAPPED === header.imageType) {
            if (header.colorMapType !== 1) {
                return false;
            }
        } else if (header.colorMapType !== 0) {
            return false;
        }

        if (header.colorMapType === 1) {
            if (header.colorMapLength === 0) {
                return false;
            }
        }

        switch (header.imageType) {
            case this.TYPE_MAPPED_RLE:
            case this.TYPE_MAPPED:
                break;

            case this.TYPE_GRAY_RLE:
            case this.TYPE_GRAY:
                break;

            case this.TYPE_COLOR_RLE:
            case this.TYPE_COLOR:
                switch (Math.floor(header.bpp / 8)) {
                    case 4:
                    case 3:
                    case 2:
                        break;

                    default:
                        return false;
                }
                break;

            default:
                return false;
        }

        if (16384 < header.width) {
            return false;
        }

        if (16384 < header.height) {
            return false;
        }

        return true;
    };

    TGALoader.prototype.expandRLE = function (data) {
        var pelbytes = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var datasize = pelbytes;
        var size = (width * height * pelbytes);
        var RLE_PACKETSIZE = this.RLE_PACKETSIZE;
        var dst = new Uint8Array(size);
        var src = 0, dest = 0, n, k;
        do {
            var count = data[src];
            src += 1;

            /*jshint bitwise: false*/
            var bytes = (((count & ~RLE_PACKETSIZE) + 1) * datasize);

            if (count & RLE_PACKETSIZE) {
                if (datasize === 1) {
                    var r = data[src];
                    src += 1;

                    for (n = 0; n < bytes; n += 1) {
                        dst[dest + k] = r;
                    }
                } else {
                    for (n = 0; n < datasize; n += 1) {
                        dst[dest + n] = data[src + n];
                    }
                    src += datasize;

                    for (k = datasize; k < bytes; k += datasize) {
                        for (n = 0; n < datasize; n += 1) {
                            dst[dest + k + n] = dst[dest + n];
                        }
                    }
                }
            } else {
                for (n = 0; n < bytes; n += 1) {
                    dst[dest + n] = data[src + n];
                }
                src += bytes;
            }

            /*jshint bitwise: true*/
            dest += bytes;
        } while(dest < size);

        return dst;
    };

    TGALoader.prototype.expandColorMap = function (data) {
        // Unpack image
        var pelbytes = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var size = (width * height * pelbytes);
        var dst = new Uint8Array(size);
        var dest = 0, src = 0;
        var palette = this.colorMap;
        delete this.colorMap;

        if (pelbytes === 2 || pelbytes === 3 || pelbytes === 4) {
            do {
                var index = (data[src] * pelbytes);
                src += 1;

                for (var n = 0; n < pelbytes; n += 1) {
                    dst[dest] = palette[index + n];
                    dest += 1;
                }
            } while(dest < size);
        }

        if (pelbytes === 2) {
            dst = this.convertARGB2RGBA(dst);
        }

        return dst;
    };

    TGALoader.prototype.flipHorz = function (data) {
        var pelbytes = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var halfWidth = Math.floor(width / 2);
        var pitch = (width * pelbytes);
        for (var i = 0; i < height; i += 1) {
            for (var j = 0; j < halfWidth; j += 1) {
                for (var k = 0; k < pelbytes; k += 1) {
                    var tmp = data[j * pelbytes + k];
                    data[j * pelbytes + k] = data[(width - j - 1) * pelbytes + k];
                    data[(width - j - 1) * pelbytes + k] = tmp;
                }
            }
            data += pitch;
        }
    };

    TGALoader.prototype.flipVert = function (data) {
        var pelbytes = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var halfHeight = Math.floor(height / 2);
        var pitch = (width * pelbytes);
        for (var i = 0; i < halfHeight; i += 1) {
            var srcRow = (i * pitch);
            var destRow = ((height - i - 1) * pitch);
            for (var j = 0; j < pitch; j += 1) {
                var tmp = data[srcRow + j];
                data[srcRow + j] = data[destRow + j];
                data[destRow + j] = tmp;
            }
        }
    };

    TGALoader.prototype.convertBGR2RGB = function (data) {
        // Rearrange the colors from BGR to RGB
        var bytesPerPixel = this.bytesPerPixel;
        var width = this.width;
        var height = this.height;
        var size = (width * height * bytesPerPixel);
        var offset = 0;
        do {
            var tmp = data[offset];
            data[offset] = data[offset + 2];
            data[offset + 2] = tmp;
            offset += bytesPerPixel;
        } while(offset < size);
    };

    TGALoader.prototype.convertARGB2RGBA = function (data) {
        // Rearrange the colors from ARGB to RGBA (2 bytes)
        var bytesPerPixel = this.bytesPerPixel;
        if (bytesPerPixel === 2) {
            var width = this.width;
            var height = this.height;
            var size = (width * height * bytesPerPixel);
            var dst = new Uint16Array(width * height);
            var src = 0, dest = 0;
            var r, g, b, a;

            /*jshint bitwise: false*/
            var mask = ((1 << 5) - 1);
            var blueMask = mask;
            var greenMask = (mask << 5);
            var redMask = (mask << 10);

            do {
                var value = ((src[1] << 8) | src[0]);
                src += 2;
                b = (value & blueMask) << 1;
                g = (value & greenMask) << 1;
                r = (value & redMask) << 1;
                a = (value >> 15);
                dst[dest] = r | g | b | a;
                dest += 1;
            } while(src < size);

            /*jshint bitwise: true*/
            return dst;
        } else {
            return data;
        }
    };

    TGALoader.create = function (params) {
        var loader = new TGALoader();
        loader.gd = params.gd;
        loader.onload = params.onload;
        loader.onerror = params.onerror;

        var src = params.src;
        if (src) {
            loader.src = src;
            var xhr;
            if (window.XMLHttpRequest) {
                xhr = new window.XMLHttpRequest();
            } else if (window.ActiveXObject) {
                xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
            } else {
                if (params.onerror) {
                    params.onerror(0);
                }
                return null;
            }

            xhr.onreadystatechange = function () {
                if (xhr.readyState === 4) {
                    if (!TurbulenzEngine || !TurbulenzEngine.isUnloading()) {
                        var xhrStatus = xhr.status;
                        var xhrStatusText = xhr.status !== 0 && xhr.statusText || 'No connection';

                        if (xhrStatus === 0 && (window.location.protocol === "file:" || window.location.protocol === "chrome-extension:")) {
                            xhrStatus = 200;
                        }

                        if (xhr.getAllResponseHeaders() === "") {
                            var noBody;
                            if (xhr.responseType === "arraybuffer") {
                                noBody = !xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                noBody = !xhr.mozResponseArrayBuffer;
                            } else {
                                noBody = !xhr.responseText;
                            }
                            if (noBody) {
                                if (loader.onerror) {
                                    loader.onerror(0);
                                }

                                // break circular reference
                                xhr.onreadystatechange = null;
                                xhr = null;
                                return;
                            }
                        }

                        if (xhrStatus === 200 || xhrStatus === 0) {
                            var buffer;
                            if (xhr.responseType === "arraybuffer") {
                                buffer = xhr.response;
                            } else if (xhr.mozResponseArrayBuffer) {
                                buffer = xhr.mozResponseArrayBuffer;
                            } else {
                                /*jshint bitwise: false*/
                                var text = xhr.responseText;
                                var numChars = text.length;
                                buffer = [];
                                buffer.length = numChars;
                                for (var i = 0; i < numChars; i += 1) {
                                    buffer[i] = (text.charCodeAt(i) & 0xff);
                                }
                                /*jshint bitwise: true*/
                            }

                            loader.processBytes(new Uint8Array(buffer));
                            if (loader.data) {
                                if (loader.onload) {
                                    loader.onload(loader.data, loader.width, loader.height, loader.format, xhrStatus);
                                }
                            } else {
                                if (loader.onerror) {
                                    loader.onerror(xhrStatus);
                                }
                            }
                        } else {
                            if (loader.onerror) {
                                loader.onerror(xhrStatus);
                            }
                        }
                    }

                    // break circular reference
                    xhr.onreadystatechange = null;
                    xhr = null;
                }
            };
            xhr.open("GET", params.src, true);
            if (typeof xhr.responseType === "string" || (xhr.hasOwnProperty && xhr.hasOwnProperty("responseType"))) {
                xhr.responseType = "arraybuffer";
            } else if (xhr.overrideMimeType) {
                xhr.overrideMimeType("text/plain; charset=x-user-defined");
            } else {
                xhr.setRequestHeader("Content-Type", "text/plain; charset=x-user-defined");
            }
            xhr.send(null);
        } else {
            loader.processBytes(params.data);
            if (loader.data) {
                if (loader.onload) {
                    loader.onload(loader.data, loader.width, loader.height, loader.format, 200);
                }
            } else {
                if (loader.onerror) {
                    loader.onerror(0);
                }
            }
        }

        return loader;
    };
    TGALoader.version = 1;
    return TGALoader;
})();

TGALoader.prototype.TYPE_MAPPED = 1;
TGALoader.prototype.TYPE_COLOR = 2;
TGALoader.prototype.TYPE_GRAY = 3;
TGALoader.prototype.TYPE_MAPPED_RLE = 9;
TGALoader.prototype.TYPE_COLOR_RLE = 10;
TGALoader.prototype.TYPE_GRAY_RLE = 11;
TGALoader.prototype.DESC_ABITS = 0x0f;
TGALoader.prototype.DESC_HORIZONTAL = 0x10;
TGALoader.prototype.DESC_VERTICAL = 0x20;
TGALoader.prototype.SIGNATURE = "TRUEVISION-XFILE";
TGALoader.prototype.RLE_PACKETSIZE = 0x80;

// Copyright (c) 2012 Turbulenz Limited
var Touch = (function () {
    function Touch() {
    }
    Touch.create = function (params) {
        var touch = new Touch();

        touch.force = params.force;
        touch.identifier = params.identifier;
        touch.isGameTouch = params.isGameTouch;
        touch.positionX = params.positionX;
        touch.positionY = params.positionY;
        touch.radiusX = params.radiusX;
        touch.radiusY = params.radiusY;
        touch.rotationAngle = params.rotationAngle;

        return touch;
    };
    return Touch;
})();

// Copyright (c) 2012 Turbulenz Limited
var WebGLTouchEvent = (function () {
    function WebGLTouchEvent() {
    }
    WebGLTouchEvent.create = function (params) {
        var touchEvent = new WebGLTouchEvent();

        touchEvent.changedTouches = params.changedTouches;
        touchEvent.gameTouches = params.gameTouches;
        touchEvent.touches = params.touches;

        return touchEvent;
    };
    return WebGLTouchEvent;
})();

// Copyright (c) 2011-2013 Turbulenz Limited
/*global VMath*/
/*global WebGLGraphicsDevice*/
/*global WebGLInputDevice*/
/*global WebGLSoundDevice*/
/*global WebGLPhysicsDevice*/
/*global WebGLNetworkDevice*/
/*global Float32Array*/
/*global console*/
/*global window*/

;

;

;

;

//
// WebGLTurbulenzEngine
//
var WebGLTurbulenzEngine = (function () {
    function WebGLTurbulenzEngine() {
        this.version = '0.28.0.0';
    }
    WebGLTurbulenzEngine.prototype.setInterval = function (f, t) {
        var that = this;
        return window.setInterval(function () {
            that.updateTime();
            f();
        }, t);
    };

    WebGLTurbulenzEngine.prototype.clearInterval = function (i) {
        window.clearInterval(i);
    };

    WebGLTurbulenzEngine.prototype.createGraphicsDevice = function (params) {
        if (this.graphicsDevice) {
            this.callOnError('GraphicsDevice already created');
            return null;
        } else {
            var graphicsDevice = WebGLGraphicsDevice.create(this.canvas, params);
            this.graphicsDevice = graphicsDevice;
            return graphicsDevice;
        }
    };

    WebGLTurbulenzEngine.prototype.createPhysicsDevice = function (params) {
        if (this.physicsDevice) {
            this.callOnError('PhysicsDevice already created');
            return null;
        } else {
            var physicsDevice;
            var plugin = this.getPluginObject();
            if (plugin) {
                physicsDevice = plugin.createPhysicsDevice(params);
            } else {
                physicsDevice = WebGLPhysicsDevice.create();
            }
            this.physicsDevice = physicsDevice;
            return physicsDevice;
        }
    };

    WebGLTurbulenzEngine.prototype.createSoundDevice = function (params) {
        if (this.soundDevice) {
            this.callOnError('SoundDevice already created');
            return null;
        } else {
            var soundDevice;
            var plugin = this.getPluginObject();
            if (plugin) {
                soundDevice = plugin.createSoundDevice(params);
            } else {
                soundDevice = WebGLSoundDevice.create(params);
            }
            this.soundDevice = soundDevice;
            return soundDevice;
        }
    };

    WebGLTurbulenzEngine.prototype.createInputDevice = function (params) {
        if (this.inputDevice) {
            this.callOnError('InputDevice already created');
            return null;
        } else {
            var inputDevice = WebGLInputDevice.create(this.canvas);
            this.inputDevice = inputDevice;
            return inputDevice;
        }
    };

    WebGLTurbulenzEngine.prototype.createNetworkDevice = function (params) {
        if (this.networkDevice) {
            throw 'NetworkDevice already created';
        } else {
            var networkDevice = WebGLNetworkDevice.create(params);
            this.networkDevice = networkDevice;
            return networkDevice;
        }
    };

    WebGLTurbulenzEngine.prototype.createMathDevice = function (params) {
        try  {
            var testVector = new Float32Array([1, 2, 3]);

            VMath.v3Build.apply(VMath, testVector);

            // Clamp FLOAT_MAX
            testVector[0] = VMath.FLOAT_MAX;
            VMath.FLOAT_MAX = testVector[0];
        } catch (e) {
        }

        return WebGLMathDevice;
    };

    WebGLTurbulenzEngine.prototype.createNativeMathDevice = function (params) {
        return WebGLMathDevice;
    };

    WebGLTurbulenzEngine.prototype.getGraphicsDevice = function () {
        var graphicsDevice = this.graphicsDevice;
        if (graphicsDevice === null) {
            this.callOnError("GraphicsDevice not created yet.");
        }
        return graphicsDevice;
    };

    WebGLTurbulenzEngine.prototype.getPhysicsDevice = function () {
        return this.physicsDevice;
    };

    WebGLTurbulenzEngine.prototype.getSoundDevice = function () {
        return this.soundDevice;
    };

    WebGLTurbulenzEngine.prototype.getInputDevice = function () {
        return this.inputDevice;
    };

    WebGLTurbulenzEngine.prototype.getNetworkDevice = function () {
        return this.networkDevice;
    };

    WebGLTurbulenzEngine.prototype.getMathDevice = function () {
        return WebGLMathDevice;
    };

    WebGLTurbulenzEngine.prototype.getNativeMathDevice = function () {
        return WebGLMathDevice;
    };

    WebGLTurbulenzEngine.prototype.getObjectStats = function () {
        return null;
    };

    WebGLTurbulenzEngine.prototype.flush = function () {
    };

    WebGLTurbulenzEngine.prototype.run = function () {
    };

    WebGLTurbulenzEngine.prototype.encrypt = function (msg) {
        return msg;
    };

    WebGLTurbulenzEngine.prototype.decrypt = function (msg) {
        return msg;
    };

    WebGLTurbulenzEngine.prototype.generateSignature = function (msg) {
        return null;
    };

    WebGLTurbulenzEngine.prototype.verifySignature = function (msg, sig) {
        return true;
    };

    WebGLTurbulenzEngine.prototype.onerror = function (msg) {
        console.error(msg);
    };

    WebGLTurbulenzEngine.prototype.onwarning = function (msg) {
        console.warn(msg);
    };

    WebGLTurbulenzEngine.prototype.onperformancewarning = function (msg) {
    };

    WebGLTurbulenzEngine.prototype.getSystemInfo = function () {
        return this.systemInfo;
    };

    WebGLTurbulenzEngine.prototype.request = function (url, callback) {
        var that = this;

        var xhr;
        if (window.XMLHttpRequest) {
            xhr = new window.XMLHttpRequest();
        } else if (window.ActiveXObject) {
            xhr = new window.ActiveXObject("Microsoft.XMLHTTP");
        } else {
            that.callOnError("No XMLHTTPRequest object could be created");
            return;
        }

        var httpRequestCallback = function httpRequestCallbackFn() {
            if (xhr.readyState === 4) {
                if (!that.isUnloading()) {
                    var xhrResponseText = xhr.responseText;
                    var xhrStatus = xhr.status;

                    if ("" === xhrResponseText) {
                        xhrResponseText = null;
                    }

                    if (xhrStatus === 0 && xhrResponseText && window.location.protocol === "file:") {
                        xhrStatus = 200;
                    } else if (null === xhr.getResponseHeader("Content-Type") && "" === xhr.getAllResponseHeaders()) {
                        // Sometimes the browser sets status to 200 OK
                        // when the connection is closed before the
                        // message is sent (weird!).  In order to address
                        // this we fail any completely empty responses.
                        // Hopefully, nobody will get a valid response
                        // with no headers and no body!
                        // Except that for cross domain requests getAllResponseHeaders ALWAYS returns an empty string
                        // even for valid responses...
                        callback(null, 0);
                        return;
                    }

                    if (xhrStatus !== 0) {
                        if (404 === xhrStatus) {
                            xhrResponseText = null;
                        }

                        callback(xhrResponseText, xhrStatus);
                    } else {
                        // Checking xhr.statusText when xhr.status is
                        // 0 causes a silent error
                        callback(xhrResponseText, 0);
                    }
                }

                // break circular reference
                xhr.onreadystatechange = null;
                xhr = null;
                callback = null;
            }
        };

        xhr.open('GET', url, true);
        if (callback) {
            xhr.onreadystatechange = httpRequestCallback;
        }
        xhr.send();
    };

    // Internals
    WebGLTurbulenzEngine.prototype.destroy = function () {
        if (this.networkDevice) {
            delete this.networkDevice;
        }
        if (this.inputDevice) {
            this.inputDevice.destroy();
            delete this.inputDevice;
        }
        if (this.physicsDevice) {
            delete this.physicsDevice;
        }
        if (this.soundDevice) {
            if (this.soundDevice.destroy) {
                this.soundDevice.destroy();
            }
            delete this.soundDevice;
        }
        if (this.graphicsDevice) {
            this.graphicsDevice.destroy();
            delete this.graphicsDevice;
        }
        if (this.canvas) {
            delete this.canvas;
        }
        if (this.resizeCanvas) {
            window.removeEventListener('resize', this.resizeCanvas, false);
            delete this.resizeCanvas;
        }
        if (this.handleZeroTimeoutMessages) {
            window.removeEventListener("message", this.handleZeroTimeoutMessages, true);
            delete this.handleZeroTimeoutMessages;
        }
    };

    WebGLTurbulenzEngine.prototype.getPluginObject = function () {
        if (!this.plugin && this.pluginId) {
            this.plugin = document.getElementById(this.pluginId);
        }
        return this.plugin;
    };

    WebGLTurbulenzEngine.prototype.unload = function () {
        if (!this.unloading) {
            this.unloading = true;
            if (this.onunload) {
                this.onunload();
            }
            if (this.destroy) {
                this.destroy();
            }
        }
    };

    WebGLTurbulenzEngine.prototype.isUnloading = function () {
        return this.unloading;
    };

    WebGLTurbulenzEngine.prototype.enableProfiling = function () {
    };

    WebGLTurbulenzEngine.prototype.startProfiling = function () {
        if (console && console.profile && console.profileEnd) {
            console.profile("turbulenz");
        }
    };

    WebGLTurbulenzEngine.prototype.stopProfiling = function () {
        // Chrome and Safari return an object. IE and Firefox print to the console/profile tab.
        var result;
        if (console && console.profile && console.profileEnd) {
            console.profileEnd();
            if (console.profiles) {
                result = console.profiles[console.profiles.length - 1];
            }
        }

        return result;
    };

    WebGLTurbulenzEngine.prototype.callOnError = function (msg) {
        var onerror = this.onerror;
        if (onerror) {
            onerror(msg);
        }
    };

    WebGLTurbulenzEngine.create = function (params) {
        var tz = new WebGLTurbulenzEngine();

        var canvas = params.canvas;
        var fillParent = params.fillParent;

        // To expose unload (the whole interaction needs a re-design)
        window.TurbulenzEngineCanvas = tz;

        tz.pluginId = params.pluginId;
        tz.plugin = null;

        // time property
        var getTime = Date.now;
        var performance = window.performance;
        if (performance) {
            if (performance.now) {
                getTime = function getTimeFn() {
                    return performance.now();
                };
            } else if ((performance).webkitNow) {
                getTime = function getTimeFn() {
                    return (performance).webkitNow();
                };
            }
        }

        // To be used by the GraphicsDevice for accurate fps calculations
        tz.getTime = getTime;

        var baseTime = getTime();

        // Safari 6.0 has broken object property defines.
        var canUseDefineProperty = true;
        var navStr = navigator.userAgent;
        var navVersionIdx = navStr.indexOf("Version/6.0");
        if (-1 !== navVersionIdx) {
            if (-1 !== navStr.substring(navVersionIdx).indexOf("Safari/")) {
                canUseDefineProperty = false;
            }
        }

        if (canUseDefineProperty && Object.defineProperty) {
            Object.defineProperty(tz, "time", {
                get: function () {
                    return ((getTime() - baseTime) * 0.001);
                },
                set: function (newValue) {
                    if (typeof newValue === 'number') {
                        // baseTime is in milliseconds, newValue is in seconds
                        baseTime = (getTime() - (newValue * 1000));
                    } else {
                        tz.callOnError("Must set 'time' attribute to a number");
                    }
                },
                enumerable: false,
                configurable: false
            });

            tz.updateTime = function () {
            };
        } else {
            tz.updateTime = function () {
                this.time = ((getTime() - baseTime) * 0.001);
            };
        }

        if (window.postMessage) {
            var zeroTimeoutMessageName = "0-timeout-message";
            var timeouts = [];
            var timeId = 0;

            var setZeroTimeout = function setZeroTimeoutFn(fn) {
                timeId += 1;
                var timeout = {
                    id: timeId,
                    fn: fn
                };
                timeouts.push(timeout);
                window.postMessage(zeroTimeoutMessageName, "*");
                return timeout;
            };

            var clearZeroTimeout = function clearZeroTimeoutFn(timeout) {
                var id = timeout.id;
                var numTimeouts = timeouts.length;
                for (var n = 0; n < numTimeouts; n += 1) {
                    if (timeouts[n].id === id) {
                        timeouts.splice(n, 1);
                        return;
                    }
                }
            };

            var handleZeroTimeoutMessages = function handleZeroTimeoutMessagesFn(event) {
                if (event.source === window && event.data === zeroTimeoutMessageName) {
                    event.stopPropagation();

                    if (timeouts.length && !tz.isUnloading()) {
                        var timeout = timeouts.shift();
                        var fn = timeout.fn;
                        fn();
                    }
                }
            };

            tz.handleZeroTimeoutMessages = handleZeroTimeoutMessages;

            window.addEventListener("message", handleZeroTimeoutMessages, true);

            tz.setTimeout = function (f, t) {
                if (t < 1) {
                    return (setZeroTimeout(f));
                } else {
                    var that = this;
                    return window.setTimeout(function () {
                        that.updateTime();
                        if (!that.isUnloading()) {
                            f();
                        }
                    }, t);
                }
            };

            tz.clearTimeout = function (i) {
                if (typeof i === 'object') {
                    return clearZeroTimeout(i);
                } else {
                    return window.clearTimeout(i);
                }
            };
        } else {
            tz.setTimeout = function (f, t) {
                var that = this;
                return window.setTimeout(function () {
                    that.updateTime();
                    if (!that.isUnloading()) {
                        f();
                    }
                }, t);
            };

            tz.clearTimeout = function (i) {
                return window.clearTimeout(i);
            };
        }

        var requestAnimationFrame = (window.requestAnimationFrame || window.webkitRequestAnimationFrame || window.oRequestAnimationFrame || window.msRequestAnimationFrame || window.mozRequestAnimationFrame);
        if (requestAnimationFrame) {
            tz.setInterval = function (f, t) {
                var that = this;
                if (Math.abs(t - (1000 / 60)) <= 1) {
                    var interval = {
                        enabled: true
                    };
                    var nextFrameTime = (getTime() + 16.6);
                    var wrap1 = function wrap1() {
                        if (interval.enabled) {
                            var currentTime = getTime();
                            var diff = (currentTime - nextFrameTime);
                            if (0 <= diff) {
                                if (diff > 50) {
                                    nextFrameTime = (currentTime + 16.6);
                                } else {
                                    nextFrameTime += 16.6;
                                }
                                that.updateTime();
                                if (!that.isUnloading()) {
                                    f();
                                }
                            }
                            requestAnimationFrame(wrap1, that.canvas);
                        }
                    };
                    requestAnimationFrame(wrap1, that.canvas);
                    return interval;
                } else {
                    var wrap2 = function wrap2() {
                        that.updateTime();
                        if (!that.isUnloading()) {
                            f();
                        }
                    };
                    return window.setInterval(wrap2, t);
                }
            };

            tz.clearInterval = function (i) {
                if (typeof i === 'object') {
                    i.enabled = false;
                } else {
                    window.clearInterval(i);
                }
            };
        }

        tz.canvas = canvas;
        tz.networkDevice = null;
        tz.inputDevice = null;
        tz.physicsDevice = null;
        tz.soundDevice = null;
        tz.graphicsDevice = null;

        if (fillParent) {
            // Resize canvas to fill parent
            tz.resizeCanvas = function () {
                if (document.fullscreenElement === canvas || document.mozFullScreenElement === canvas || document.webkitFullscreenElement === canvas || document.msFullscreenElement === canvas) {
                    canvas.width = window.innerWidth;
                    canvas.height = window.innerHeight;
                } else {
                    var parentNode = canvas.parentNode;
                    canvas.width = parentNode.clientWidth;
                    canvas.height = parentNode.clientHeight;
                }
            };

            tz.resizeCanvas();

            window.addEventListener('resize', tz.resizeCanvas, false);
        }

        try  {
            var previousOnBeforeUnload = window.onbeforeunload;
            window.onbeforeunload = function () {
                tz.unload();

                if (previousOnBeforeUnload) {
                    previousOnBeforeUnload.call(this);
                }
            };
        } catch (e) {
            // If the game is running as a CWS packaged app then onbeforeunload is not available
        }
        ;

        tz.time = 0;

        // System info
        var systemInfo = {
            architecture: '',
            cpuDescription: '',
            cpuVendor: '',
            numPhysicalCores: 1,
            numLogicalCores: 1,
            ramInMegabytes: 0,
            frequencyInMegaHZ: 0,
            osVersionMajor: 0,
            osVersionMinor: 0,
            osVersionBuild: 0,
            osName: navigator.platform,
            platformProfile: "desktop",
            userLocale: (navigator.language || navigator.userLanguage).replace('-', '_')
        };

        var looksLikeNetbook = function looksLikeNetbookFn() {
            var minScreenDim = Math.min(window.screen.height, window.screen.width);
            return minScreenDim < 900;
        };

        var userAgent = navigator.userAgent;
        var osIndex = userAgent.indexOf('Windows');
        if (osIndex !== -1) {
            systemInfo.osName = 'Windows';
            if (navigator.platform === 'Win64') {
                systemInfo.architecture = 'x86_64';
            } else if (navigator.platform === 'Win32') {
                systemInfo.architecture = 'x86';
            }
            osIndex += 7;
            if (userAgent.slice(osIndex, (osIndex + 4)) === ' NT ') {
                osIndex += 4;
                systemInfo.osVersionMajor = parseInt(userAgent.slice(osIndex, (osIndex + 1)), 10);
                systemInfo.osVersionMinor = parseInt(userAgent.slice((osIndex + 2), (osIndex + 4)), 10);
            }
            if (looksLikeNetbook()) {
                systemInfo.platformProfile = "tablet";
                /* if (debug) {
                    debug.log("Setting platformProfile to 'tablet'");
                } */
            }
        } else {
            osIndex = userAgent.indexOf('Mac OS X');
            if (osIndex !== -1) {
                systemInfo.osName = 'Darwin';
                if (navigator.platform.indexOf('Intel') !== -1) {
                    systemInfo.architecture = 'x86';
                }
                osIndex += 9;
                systemInfo.osVersionMajor = parseInt(userAgent.slice(osIndex, (osIndex + 2)), 10);
                systemInfo.osVersionMinor = parseInt(userAgent.slice((osIndex + 3), (osIndex + 4)), 10);
                systemInfo.osVersionBuild = (parseInt(userAgent.slice((osIndex + 5), (osIndex + 6)), 10) || 0);
            } else {
                osIndex = userAgent.indexOf('Linux');
                if (osIndex !== -1) {
                    systemInfo.osName = 'Linux';
                    if (navigator.platform.indexOf('64') !== -1) {
                        systemInfo.architecture = 'x86_64';
                    } else if (navigator.platform.indexOf('x86') !== -1) {
                        systemInfo.architecture = 'x86';
                    }
                    if (looksLikeNetbook()) {
                        systemInfo.platformProfile = "tablet";
                        /* if (debug) {
                            debug.log("Setting platformProfile to 'tablet'");
                        } */
                    }
                } else {
                    osIndex = userAgent.indexOf('Android');
                    if (-1 !== osIndex) {
                        systemInfo.osName = 'Android';
                        if (navigator.platform.indexOf('arm')) {
                            systemInfo.architecture = 'arm';
                        } else if (navigator.platform.indexOf('x86')) {
                            systemInfo.architecture = 'x86';
                        }
                        if (-1 !== userAgent.indexOf('Mobile')) {
                            systemInfo.platformProfile = "smartphone";
                        } else {
                            systemInfo.platformProfile = "tablet";
                        }
                    } else {
                        if (-1 !== userAgent.indexOf("iPhone") || -1 !== userAgent.indexOf("iPod")) {
                            systemInfo.osName = 'iOS';
                            systemInfo.architecture = 'arm';
                            systemInfo.platformProfile = 'smartphone';
                        } else if (-1 !== userAgent.indexOf("iPad")) {
                            systemInfo.osName = 'iOS';
                            systemInfo.architecture = 'arm';
                            systemInfo.platformProfile = 'tablet';
                        }
                    }
                }
            }
        }
        tz.systemInfo = systemInfo;

        var b64ConversionTable = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/=".split('');

        tz.base64Encode = function base64EncodeFn(bytes) {
            var output = "";
            var numBytes = bytes.length;
            var valueToChar = b64ConversionTable;
            var n, chr1, chr2, chr3, enc1, enc2, enc3, enc4;

            /*jshint bitwise: false*/
            n = 0;
            while (n < numBytes) {
                chr1 = bytes[n];
                n += 1;

                enc1 = (chr1 >> 2);

                if (n < numBytes) {
                    chr2 = bytes[n];
                    n += 1;

                    if (n < numBytes) {
                        chr3 = bytes[n];
                        n += 1;

                        enc2 = (((chr1 & 3) << 4) | (chr2 >> 4));
                        enc3 = (((chr2 & 15) << 2) | (chr3 >> 6));
                        enc4 = (chr3 & 63);
                    } else {
                        enc2 = (((chr1 & 3) << 4) | (chr2 >> 4));
                        enc3 = ((chr2 & 15) << 2);
                        enc4 = 64;
                    }
                } else {
                    enc2 = ((chr1 & 3) << 4);
                    enc3 = 64;
                    enc4 = 64;
                }

                output += valueToChar[enc1];
                output += valueToChar[enc2];
                output += valueToChar[enc3];
                output += valueToChar[enc4];
            }

            /*jshint bitwise: true*/
            return output;
        };

        return tz;
    };
    return WebGLTurbulenzEngine;
})();

window.WebGLTurbulenzEngine = WebGLTurbulenzEngine;

/*
* @author: 7inpham@gmail.com
* @description: Main entry point with the forever loop
*/

// Copyright (c) 2009-2014 Turbulenz Limited
//
// Camera
//
var Camera = (function () {
    function Camera() {
        // updateProjectionMatrix(): void;
        // updateFrustumPlanes(): void;
        this.viewOffsetX = 0.0;
        this.viewOffsetY = 0.0;
        this.recipViewWindowX = 1.0 / 1.0;
        this.recipViewWindowY = 1.0 / 1.0;
        this.infinite = false;
        this.parallel = false;
        this.aspectRatio = 4.0 / 3.0;
        this.nearPlane = 1.0;
        this.farPlane = 1000.0;
    }
    Camera.prototype.lookAt = function (lookAt, up, eyePosition) {
        var md = this.md;
        var v3Normalize = md.v3Normalize;
        var v3Cross = md.v3Cross;
        var zaxis = md.v3Sub(eyePosition, lookAt);

        v3Normalize.call(md, zaxis, zaxis);
        var xaxis = v3Cross.call(md, v3Normalize.call(md, up, up), zaxis);
        v3Normalize.call(md, xaxis, xaxis);
        var yaxis = v3Cross.call(md, zaxis, xaxis);
        this.matrix = md.m43Build(xaxis, yaxis, zaxis, eyePosition, this.matrix);
    };

    Camera.prototype.updateProjectionMatrix = function () {
        var rcpvwX = this.recipViewWindowX;
        var rcpvwY = this.recipViewWindowY * this.aspectRatio;
        var shearX = rcpvwX * this.viewOffsetX;
        var shearY = rcpvwY * this.viewOffsetY;
        var far = this.farPlane;
        var near = this.nearPlane;

        var rcpfn;
        if (far !== near) {
            rcpfn = (1.0 / (far - near));
        } else {
            rcpfn = 0.0;
        }

        var z0, z1, w0, w1;
        if (this.parallel) {
            z0 = -2.0 * rcpfn;
            w0 = (-(far + near) * rcpfn);
            z1 = 0.0;
            w1 = 1.0;
        } else {
            if (this.infinite) {
                z0 = -1.0;
            } else {
                z0 = (-(far + near) * rcpfn);
                //z0 = (far * rcpfn);
            }

            w0 = -(2.0 * far * near * rcpfn);

            //w0 = (-z0 * near);
            z1 = -1.0;
            w1 = 0.0;
        }

        this.projectionMatrix = this.md.m44Build(rcpvwX, 0.0, 0.0, 0.0, 0.0, rcpvwY, 0.0, 0.0, -shearX, -shearY, z0, z1, 0.0, 0.0, w0, w1, this.projectionMatrix);
    };

    Camera.prototype.updateViewMatrix = function () {
        var md = this.md;
        this.viewMatrix = md.m43InverseOrthonormal(this.matrix, this.viewMatrix);
    };

    Camera.prototype.updateViewProjectionMatrix = function () {
        var md = this.md;
        this.viewProjectionMatrix = md.m43MulM44(this.viewMatrix, this.projectionMatrix, this.viewProjectionMatrix);
    };

    Camera.prototype.extractFrustumPlanes = function (m, p) {
        var md = this.md;
        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];
        var m12 = m[12];
        var m13 = m[13];
        var m14 = m[14];
        var m15 = m[15];
        var planes = (p || []);

        // Negate 'd' here to avoid doing it on the isVisible functions
        var vec = md.v4Build((m3 + m0), (m7 + m4), (m11 + m8), -(m15 + m12));
        planes[0] = md.planeNormalize(vec, planes[0]);

        md.v4Build((m3 - m0), (m7 - m4), (m11 - m8), -(m15 - m12), vec);
        planes[1] = md.planeNormalize(vec, planes[1]);

        md.v4Build((m3 - m1), (m7 - m5), (m11 - m9), -(m15 - m13), vec);
        planes[2] = md.planeNormalize(vec, planes[2]);

        md.v4Build((m3 + m1), (m7 + m5), (m11 + m9), -(m15 + m13), vec);
        planes[3] = md.planeNormalize(vec, planes[3]);

        md.v4Build((m3 + m2), (m7 + m6), (m11 + m10), -(m15 + m14), vec);
        planes[4] = md.planeNormalize(vec, planes[4]);

        md.v4Build((m3 - m2), (m7 - m6), (m11 - m10), -(m15 - m14), vec);
        planes[5] = md.planeNormalize(vec, planes[5]);

        return planes;
    };

    Camera.prototype.updateFrustumPlanes = function () {
        this.frustumPlanes = this.extractFrustumPlanes(this.viewProjectionMatrix, this.frustumPlanes);
    };

    Camera.prototype.isVisiblePoint = function (p) {
        var md = this.md;
        return md.isInsidePlanesPoint(p, this.frustumPlanes);
    };

    Camera.prototype.isVisibleSphere = function (c, r) {
        var md = this.md;
        return md.isInsidePlanesSphere(c, r, this.frustumPlanes);
    };

    Camera.prototype.isVisibleBox = function (c, h) {
        var md = this.md;
        return md.isInsidePlanesBox(c, h, this.frustumPlanes);
    };

    Camera.prototype.isVisibleAABB = function (extents) {
        var md = this.md;
        return md.aabbIsInsidePlanes(extents, this.frustumPlanes);
    };

    Camera.prototype.isFullyInsideAABB = function (extents) {
        var md = this.md;
        return md.aabbIsFullyInsidePlanes(extents, this.frustumPlanes);
    };

    Camera.prototype.getFrustumPoints = function (farPlane, nearPlane, points) {
        var md = this.md;
        var viewOffsetX = this.viewOffsetX;
        var viewOffsetY = this.viewOffsetY;

        var viewWindowX = 1.0 / this.recipViewWindowX;
        var viewWindowY = 1.0 / (this.recipViewWindowY * this.aspectRatio);

        var transform = this.matrix;

        var farClip = farPlane || this.farPlane;
        var nearClip = (nearPlane !== undefined ? nearPlane : this.nearPlane);

        var frustumPoints = points || new Array(8);

        if (!this.parallel) {
            var co0 = ((transform[0] * viewOffsetX) + (transform[3] * viewOffsetY));
            var co1 = ((transform[1] * viewOffsetX) + (transform[4] * viewOffsetY));
            var co2 = ((transform[2] * viewOffsetX) + (transform[5] * viewOffsetY));

            var right0 = (transform[0] * viewWindowX);
            var right1 = (transform[1] * viewWindowX);
            var right2 = (transform[2] * viewWindowX);
            var up0 = (transform[3] * viewWindowY);
            var up1 = (transform[4] * viewWindowY);
            var up2 = (transform[5] * viewWindowY);
            var at0 = (co0 - transform[6]);
            var at1 = (co1 - transform[7]);
            var at2 = (co2 - transform[8]);
            var pos0 = (transform[9] + co0);
            var pos1 = (transform[10] + co1);
            var pos2 = (transform[11] + co2);

            var dirTR0 = (at0 + right0 + up0);
            var dirTR1 = (at1 + right1 + up1);
            var dirTR2 = (at2 + right2 + up2);
            var dirTL0 = (at0 - right0 + up0);
            var dirTL1 = (at1 - right1 + up1);
            var dirTL2 = (at2 - right2 + up2);
            var dirBL0 = (at0 - right0 - up0);
            var dirBL1 = (at1 - right1 - up1);
            var dirBL2 = (at2 - right2 - up2);
            var dirBR0 = (at0 + right0 - up0);
            var dirBR1 = (at1 + right1 - up1);
            var dirBR2 = (at2 + right2 - up2);

            /* tslint:disable:max-line-length */
            frustumPoints[0] = md.v3Build((pos0 + (dirTR0 * nearClip)), (pos1 + (dirTR1 * nearClip)), (pos2 + (dirTR2 * nearClip)), frustumPoints[0]);
            frustumPoints[1] = md.v3Build((pos0 + (dirTL0 * nearClip)), (pos1 + (dirTL1 * nearClip)), (pos2 + (dirTL2 * nearClip)), frustumPoints[1]);
            frustumPoints[2] = md.v3Build((pos0 + (dirBL0 * nearClip)), (pos1 + (dirBL1 * nearClip)), (pos2 + (dirBL2 * nearClip)), frustumPoints[2]);
            frustumPoints[3] = md.v3Build((pos0 + (dirBR0 * nearClip)), (pos1 + (dirBR1 * nearClip)), (pos2 + (dirBR2 * nearClip)), frustumPoints[3]);
            frustumPoints[4] = md.v3Build((pos0 + (dirTR0 * farClip)), (pos1 + (dirTR1 * farClip)), (pos2 + (dirTR2 * farClip)), frustumPoints[4]);
            frustumPoints[5] = md.v3Build((pos0 + (dirTL0 * farClip)), (pos1 + (dirTL1 * farClip)), (pos2 + (dirTL2 * farClip)), frustumPoints[5]);
            frustumPoints[6] = md.v3Build((pos0 + (dirBL0 * farClip)), (pos1 + (dirBL1 * farClip)), (pos2 + (dirBL2 * farClip)), frustumPoints[6]);
            frustumPoints[7] = md.v3Build((pos0 + (dirBR0 * farClip)), (pos1 + (dirBR1 * farClip)), (pos2 + (dirBR2 * farClip)), frustumPoints[7]);
            /* tslint:enable:max-line-length */
        } else {
            var noffsetx = (1.0 - nearClip) * viewOffsetX;
            var foffsetx = (1.0 - farClip) * viewOffsetX;
            var noffsety = (1.0 - nearClip) * viewOffsetY;
            var foffsety = (1.0 - farClip) * viewOffsetY;

            /* tslint:disable:max-line-length */
            frustumPoints[0] = md.v3Build((viewWindowX + noffsetx), (viewWindowY + noffsety), nearClip, frustumPoints[0]);
            frustumPoints[1] = md.v3Build((noffsetx - viewWindowX), (viewWindowY + noffsety), nearClip, frustumPoints[1]);
            frustumPoints[2] = md.v3Build((noffsetx - viewWindowX), (noffsety - viewWindowY), nearClip, frustumPoints[2]);
            frustumPoints[3] = md.v3Build((viewWindowX + noffsetx), (noffsety - viewWindowY), nearClip, frustumPoints[3]);
            frustumPoints[4] = md.v3Build((viewWindowX + foffsetx), (viewWindowY + foffsety), farClip, frustumPoints[4]);
            frustumPoints[5] = md.v3Build((foffsetx - viewWindowX), (viewWindowY + foffsety), farClip, frustumPoints[5]);
            frustumPoints[6] = md.v3Build((foffsetx - viewWindowX), (foffsety - viewWindowY), farClip, frustumPoints[6]);
            frustumPoints[7] = md.v3Build((viewWindowX + foffsetx), (foffsety - viewWindowY), farClip, frustumPoints[7]);

            /* tslint:enable:max-line-length */
            md.m43TransformPoint(transform, frustumPoints[0], frustumPoints[0]);
            md.m43TransformPoint(transform, frustumPoints[1], frustumPoints[1]);
            md.m43TransformPoint(transform, frustumPoints[2], frustumPoints[2]);
            md.m43TransformPoint(transform, frustumPoints[3], frustumPoints[3]);
            md.m43TransformPoint(transform, frustumPoints[4], frustumPoints[4]);
            md.m43TransformPoint(transform, frustumPoints[5], frustumPoints[5]);
            md.m43TransformPoint(transform, frustumPoints[6], frustumPoints[6]);
            md.m43TransformPoint(transform, frustumPoints[7], frustumPoints[7]);
        }

        return frustumPoints;
    };

    Camera.prototype.getFrustumFarPoints = function (farPlane, points) {
        var md = this.md;
        var viewOffsetX = this.viewOffsetX;
        var viewOffsetY = this.viewOffsetY;
        var viewWindowX = 1.0 / this.recipViewWindowX;
        var viewWindowY = 1.0 / (this.recipViewWindowY * this.aspectRatio);
        var transform = this.matrix;
        var farClip = farPlane || this.farPlane;

        var frustumPoints = points || new Array(4);

        if (!this.parallel) {
            var t0 = transform[0];
            var t1 = transform[1];
            var t2 = transform[2];
            var t3 = transform[3];
            var t4 = transform[4];
            var t5 = transform[5];
            var t6 = transform[6];
            var t7 = transform[7];
            var t8 = transform[8];
            var t9 = transform[9];
            var t10 = transform[10];
            var t11 = transform[11];

            var co0 = ((t0 * viewOffsetX) + (t3 * viewOffsetY));
            var co1 = ((t1 * viewOffsetX) + (t4 * viewOffsetY));
            var co2 = ((t2 * viewOffsetX) + (t5 * viewOffsetY));

            var right0 = (t0 * viewWindowX);
            var right1 = (t1 * viewWindowX);
            var right2 = (t2 * viewWindowX);
            var up0 = (t3 * viewWindowY);
            var up1 = (t4 * viewWindowY);
            var up2 = (t5 * viewWindowY);
            var at0 = (co0 - t6);
            var at1 = (co1 - t7);
            var at2 = (co2 - t8);
            var pos0 = (t9 + co0);
            var pos1 = (t10 + co1);
            var pos2 = (t11 + co2);

            var dirTR0 = ((at0 + right0 + up0) * farClip);
            var dirTR1 = ((at1 + right1 + up1) * farClip);
            var dirTR2 = ((at2 + right2 + up2) * farClip);
            var dirTL0 = ((at0 - right0 + up0) * farClip);
            var dirTL1 = ((at1 - right1 + up1) * farClip);
            var dirTL2 = ((at2 - right2 + up2) * farClip);
            var dirBL0 = ((at0 - right0 - up0) * farClip);
            var dirBL1 = ((at1 - right1 - up1) * farClip);
            var dirBL2 = ((at2 - right2 - up2) * farClip);
            var dirBR0 = ((at0 + right0 - up0) * farClip);
            var dirBR1 = ((at1 + right1 - up1) * farClip);
            var dirBR2 = ((at2 + right2 - up2) * farClip);

            frustumPoints[0] = md.v3Build((pos0 + dirTR0), (pos1 + dirTR1), (pos2 + dirTR2), frustumPoints[0]);
            frustumPoints[1] = md.v3Build((pos0 + dirTL0), (pos1 + dirTL1), (pos2 + dirTL2), frustumPoints[1]);
            frustumPoints[2] = md.v3Build((pos0 + dirBL0), (pos1 + dirBL1), (pos2 + dirBL2), frustumPoints[2]);
            frustumPoints[3] = md.v3Build((pos0 + dirBR0), (pos1 + dirBR1), (pos2 + dirBR2), frustumPoints[3]);
        } else {
            var offsetX = (1.0 - farClip) * viewOffsetX;
            var offsetY = (1.0 - farClip) * viewOffsetY;
            frustumPoints[0] = md.v3Build((viewWindowX + offsetX), (viewWindowY + offsetY), farClip, frustumPoints[0]);
            frustumPoints[1] = md.v3Build((offsetX - viewWindowX), (viewWindowY + offsetY), farClip, frustumPoints[1]);
            frustumPoints[2] = md.v3Build((offsetX - viewWindowX), (offsetY - viewWindowY), farClip, frustumPoints[2]);
            frustumPoints[3] = md.v3Build((viewWindowX + offsetX), (offsetY - viewWindowY), farClip, frustumPoints[3]);
            md.m43TransformPoint(transform, frustumPoints[0], frustumPoints[0]);
            md.m43TransformPoint(transform, frustumPoints[1], frustumPoints[1]);
            md.m43TransformPoint(transform, frustumPoints[2], frustumPoints[2]);
            md.m43TransformPoint(transform, frustumPoints[3], frustumPoints[3]);
        }

        return frustumPoints;
    };

    Camera.prototype.getFrustumExtents = function (extents, farClip, nearClip) {
        var frustumPoints = this.getFrustumPoints(farClip, nearClip);
        var frustumPoint = frustumPoints[0];
        var min0 = frustumPoint[0];
        var min1 = frustumPoint[1];
        var min2 = frustumPoint[2];
        var max0 = min0;
        var max1 = min1;
        var max2 = min2;
        for (var i = 1; i < 8; i += 1) {
            frustumPoint = frustumPoints[i];
            var p0 = frustumPoint[0];
            var p1 = frustumPoint[1];
            var p2 = frustumPoint[2];
            if (min0 > p0) {
                min0 = p0;
            } else if (max0 < p0) {
                max0 = p0;
            }
            if (min1 > p1) {
                min1 = p1;
            } else if (max1 < p1) {
                max1 = p1;
            }
            if (min2 > p2) {
                min2 = p2;
            } else if (max2 < p2) {
                max2 = p2;
            }
        }
        extents[0] = min0;
        extents[1] = min1;
        extents[2] = min2;
        extents[3] = max0;
        extents[4] = max1;
        extents[5] = max2;
    };

    Camera.create = // Constructor function
    function (md) {
        var c = new Camera();
        c.md = md;
        c.matrix = md.m43BuildIdentity();
        c.viewMatrix = md.m43BuildIdentity();
        c.updateProjectionMatrix();
        c.viewProjectionMatrix = c.projectionMatrix.slice();
        c.frustumPlanes = [];
        c.updateFrustumPlanes();
        return c;
    };
    Camera.version = 1;
    return Camera;
})();

;

var CameraController = (function () {
    function CameraController() {
        /* tslint:enable:no-unused-variable */
        this.rotateSpeed = 2.0;
        this.maxSpeed = 1;
        this.mouseRotateFactor = 0.1;
    }
    CameraController.prototype.rotate = function (turn, pitch) {
        var degreestoradians = (Math.PI / 180.0);
        var md = this.md;
        var matrix = this.camera.matrix;
        var pos = md.m43Pos(matrix);
        md.m43SetPos(matrix, md.v3BuildZero());

        var rotate;
        if (pitch !== 0.0) {
            pitch *= this.rotateSpeed * degreestoradians;
            pitch *= this.mouseRotateFactor;

            var right = md.v3Normalize(md.m43Right(matrix));
            md.m43SetRight(matrix, right);

            rotate = md.m43FromAxisRotation(right, pitch);

            matrix = md.m43Mul(matrix, rotate);
        }

        if (turn !== 0.0) {
            turn *= this.rotateSpeed * degreestoradians;
            turn *= this.mouseRotateFactor;

            rotate = md.m43FromAxisRotation(md.v3BuildYAxis(), turn);

            matrix = md.m43Mul(matrix, rotate);
        }

        md.m43SetPos(matrix, pos);

        this.camera.matrix = matrix;
    };

    CameraController.prototype.translate = function (right, up, forward) {
        var md = this.md;
        var matrix = this.camera.matrix;
        var pos = md.m43Pos(matrix);
        var speed = this.maxSpeed;
        pos = md.v3Add4(pos, md.v3ScalarMul(md.m43Right(matrix), (speed * right)), md.v3ScalarMul(md.m43Up(matrix), (speed * up)), md.v3ScalarMul(md.m43At(matrix), -(speed * forward)));
        md.m43SetPos(matrix, pos);
    };

    CameraController.prototype.update = function () {
        var updateMatrix = false;

        if (this.turn !== 0.0 || this.pitch !== 0.0) {
            updateMatrix = true;

            this.rotate(this.turn, this.pitch);

            this.turn = 0.0;
            this.pitch = 0.0;
        }

        if (this.step > 0) {
            this.forward += this.step;
        } else if (this.step < 0) {
            this.backward -= this.step;
        }

        var right = ((this.right + this.padright) - (this.left + this.padleft));
        var up = this.up - this.down;
        var forward = ((this.forward + this.padforward) - (this.backward + this.padbackward));
        if (right !== 0.0 || up !== 0.0 || forward !== 0.0) {
            updateMatrix = true;

            this.translate(right, up, forward);

            if (this.step > 0) {
                this.forward -= this.step;
                this.step = 0.0;
            } else if (this.step < 0) {
                this.backward += this.step;
                this.step = 0.0;
            }
        }

        if (updateMatrix) {
            this.camera.updateViewMatrix();
        }
    };

    CameraController.create = function (gd, id, camera, log) {
        var c = new CameraController();

        c.md = camera.md;
        c.camera = camera;
        c.turn = 0.0;
        c.pitch = 0.0;
        c.right = 0.0;
        c.left = 0.0;
        c.up = 0.0;
        c.down = 0.0;
        c.forward = 0.0;
        c.backward = 0.0;
        c.step = 0.0;
        c.padright = 0.0;
        c.padleft = 0.0;
        c.padforward = 0.0;
        c.padbackward = 0.0;
        c.looktouch = {
            id: -1,
            originX: 0,
            originY: 0
        };
        c.movetouch = {
            id: -1,
            originX: 0,
            originY: 0
        };

        var keyCodes;

        if (id) {
            keyCodes = id.keyCodes;
        }

        // keyboard handling
        var onkeydownFn = function (keynum) {
            switch (keynum) {
                case keyCodes.A:
                case keyCodes.LEFT:
                case keyCodes.NUMPAD_4:
                    c.left = 1.0;
                    break;

                case keyCodes.D:
                case keyCodes.RIGHT:
                case keyCodes.NUMPAD_6:
                    c.right = 1.0;
                    break;

                case keyCodes.W:
                case keyCodes.UP:
                case keyCodes.NUMPAD_8:
                    c.forward = 1.0;
                    break;

                case keyCodes.S:
                case keyCodes.DOWN:
                case keyCodes.NUMPAD_2:
                    c.backward = 1.0;
                    break;

                case keyCodes.E:
                case keyCodes.NUMPAD_9:
                    c.up = 1.0;
                    break;

                case keyCodes.Q:
                case keyCodes.NUMPAD_7:
                    c.down = 1.0;
                    break;
            }
        };

        var onkeyupFn = function (keynum) {
            switch (keynum) {
                case keyCodes.A:
                case keyCodes.LEFT:
                case keyCodes.NUMPAD_4:
                    c.left = 0.0;
                    break;

                case keyCodes.D:
                case keyCodes.RIGHT:
                case keyCodes.NUMPAD_6:
                    c.right = 0.0;
                    break;

                case keyCodes.W:
                case keyCodes.UP:
                case keyCodes.NUMPAD_8:
                    c.forward = 0.0;
                    break;

                case keyCodes.S:
                case keyCodes.DOWN:
                case keyCodes.NUMPAD_2:
                    c.backward = 0.0;
                    break;

                case keyCodes.E:
                case keyCodes.NUMPAD_9:
                    c.up = 0.0;
                    break;

                case keyCodes.Q:
                case keyCodes.NUMPAD_7:
                    c.down = 0.0;
                    break;

                case keyCodes.RETURN:
                    gd.fullscreen = !gd.fullscreen;
                    break;
            }
        };

        if (log) {
            c.onkeydown = function onkeydownLogFn(keynum) {
                log.innerHTML += " KeyDown:&nbsp;" + keynum;
                onkeydownFn(keynum);
            };

            c.onkeyup = function onkeyupLogFn(keynum) {
                if (keynum === keyCodes.BACKSPACE) {
                    log.innerHTML = "";
                } else {
                    log.innerHTML += " KeyUp:&nbsp;" + keynum;
                }
                onkeyupFn(keynum);
            };
        } else {
            c.onkeydown = onkeydownFn;
            c.onkeyup = onkeyupFn;
        }

        // Mouse handling
        c.onmouseup = function onmouseupFn(/* button, x, y */ ) {
            if (!id.isLocked()) {
                id.lockMouse();
            }
        };

        c.onmousewheel = function onmousewheelFn(delta) {
            c.step = delta * 5;
        };

        c.onmousemove = function onmousemoveFn(deltaX, deltaY) {
            c.turn += deltaX;
            c.pitch += deltaY;
        };

        // Pad handling
        c.onpadmove = function onpadmoveFn(lX, lY, lZ, rX, rY/*, rZ, dpadState */ ) {
            c.turn += lX * 10.0;
            c.pitch += lY * 10.0;

            if (rX >= 0) {
                c.padright = rX;
                c.padleft = 0;
            } else {
                c.padright = 0;
                c.padleft = -rX;
            }

            if (rY >= 0) {
                c.padforward = rY;
                c.padbackward = 0.0;
            } else {
                c.padforward = 0.0;
                c.padbackward = -rY;
            }
        };

        c.onmouselocklost = function onmouselocklostFn() {
            id.unlockMouse();
        };

        c.ontouchstart = function ontouchstartFn(touchEvent) {
            var changedTouches = touchEvent.changedTouches;
            var numTouches = changedTouches.length;
            var t;
            var halfScreenWidth = gd.width * 0.5;
            for (t = 0; t < numTouches; t += 1) {
                var touchId = changedTouches[t].identifier;
                var touchX = changedTouches[t].positionX;
                var touchY = changedTouches[t].positionY;
                if (touchX < halfScreenWidth && c.looktouch.id === -1) {
                    c.looktouch.id = touchId;
                    c.looktouch.originX = touchX;
                    c.looktouch.originY = touchY;
                } else if (touchX >= halfScreenWidth && c.movetouch.id === -1) {
                    c.movetouch.id = touchId;
                    c.movetouch.originX = touchX;
                    c.movetouch.originY = touchY;
                }
            }
        };

        c.ontouchend = function ontouchendFn(touchEvent) {
            var changedTouches = touchEvent.changedTouches;
            var numTouches = changedTouches.length;
            var t;
            for (t = 0; t < numTouches; t += 1) {
                var touchId = changedTouches[t].identifier;
                if (c.looktouch.id === touchId) {
                    c.looktouch.id = -1;
                    c.looktouch.originX = 0;
                    c.looktouch.originY = 0;
                    c.turn = 0;
                    c.pitch = 0;
                } else if (c.movetouch.id === touchId) {
                    c.movetouch.id = -1;
                    c.movetouch.originX = 0;
                    c.movetouch.originY = 0;
                    c.left = 0.0;
                    c.right = 0.0;
                    c.forward = 0.0;
                    c.backward = 0.0;
                }
            }
        };

        c.ontouchmove = function ontouchmoveFn(touchEvent) {
            var changedTouches = touchEvent.changedTouches;
            var numTouches = changedTouches.length;
            var deadzone = 16.0;
            var t;
            for (t = 0; t < numTouches; t += 1) {
                var touchId = changedTouches[t].identifier;
                var touchX = changedTouches[t].positionX;
                var touchY = changedTouches[t].positionY;
                if (c.looktouch.id === touchId) {
                    if (touchX - c.looktouch.originX > deadzone || touchX - c.looktouch.originX < -deadzone) {
                        c.turn = (touchX - c.looktouch.originX) / deadzone;
                    } else {
                        c.turn = 0.0;
                    }
                    if (touchY - c.looktouch.originY > deadzone || touchY - c.looktouch.originY < -deadzone) {
                        c.pitch = (touchY - c.looktouch.originY) / 16.0;
                    } else {
                        c.pitch = 0.0;
                    }
                } else if (c.movetouch.id === touchId) {
                    if (touchX - c.movetouch.originX > deadzone) {
                        c.left = 0.0;
                        c.right = 1.0;
                    } else if (touchX - c.movetouch.originX < -deadzone) {
                        c.left = 1.0;
                        c.right = 0.0;
                    } else {
                        c.left = 0.0;
                        c.right = 0.0;
                    }
                    if (touchY - c.movetouch.originY > deadzone) {
                        c.forward = 0.0;
                        c.backward = 1.0;
                    } else if (touchY - c.movetouch.originY < -deadzone) {
                        c.forward = 1.0;
                        c.backward = 0.0;
                    } else {
                        c.forward = 0.0;
                        c.backward = 0.0;
                    }
                }
            }
        };

        // Attach to an InputDevice
        c.attach = function attachFn(id) {
            id.addEventListener('keydown', c.onkeydown);
            id.addEventListener('keyup', c.onkeyup);
            id.addEventListener('mouseup', c.onmouseup);
            id.addEventListener('mousewheel', c.onmousewheel);
            id.addEventListener('mousemove', c.onmousemove);
            id.addEventListener('padmove', c.onpadmove);
            id.addEventListener('mouselocklost', c.onmouselocklost);
            id.addEventListener('touchstart', c.ontouchstart);
            id.addEventListener('touchend', c.ontouchend);
            id.addEventListener('touchmove', c.ontouchmove);
        };

        if (id) {
            c.attach(id);
        }

        return c;
    };
    CameraController.version = 1;
    return CameraController;
})();

// Copyright (c) 2009-2014 Turbulenz Limited
var Floor = (function () {
    function Floor() {
    }
    Floor.create = // Constructor function
    function (gd, md) {
        var f = new Floor();

        var technique = null;
        var primitive = gd.PRIMITIVE_LINES;
        var vertexFormats = [gd.VERTEXFORMAT_FLOAT2];
        var semantics = gd.createSemantics([gd.SEMANTIC_POSITION]);
        var techniqueParameters = gd.createTechniqueParameters();

        var maxValue = Number.MAX_VALUE;
        var abs = Math.abs;
        var floor = Math.floor;
        var ceil = Math.ceil;

        var frustumMinX = maxValue;
        var frustumMinZ = maxValue;
        var frustumMaxX = -maxValue;
        var frustumMaxZ = -maxValue;

        var addPoint = function addPointFn(px, pz) {
            if (frustumMinX > px) {
                frustumMinX = px;
            }
            if (frustumMinZ > pz) {
                frustumMinZ = pz;
            }
            if (frustumMaxX < px) {
                frustumMaxX = px;
            }
            if (frustumMaxZ < pz) {
                frustumMaxZ = pz;
            }
        };

        var intersect = function intersetFn(s, e) {
            var sy = s[1];
            var ey = e[1];
            var t;
            if (sy > 0.0) {
                if (ey < 0.0) {
                    t = ((-sy) / (ey - sy));
                    addPoint(s[0] + t * (e[0] - s[0]), s[2] + t * (e[2] - s[2]));
                } else if (ey === 0.0) {
                    addPoint(e[0], e[2]);
                }
            } else if (sy < 0.0) {
                if (ey > 0.0) {
                    t = ((-sy) / (ey - sy));
                    addPoint(s[0] + t * (e[0] - s[0]), s[2] + t * (e[2] - s[2]));
                } else if (ey === 0.0) {
                    addPoint(e[0], e[2]);
                }
            } else {
                addPoint(s[0], s[2]);
                if (ey === 0.0) {
                    addPoint(e[0], e[2]);
                }
            }
        };

        f.render = function floorRenderFn(gd, camera) {
            // Calculate intersection with floor
            frustumMinX = maxValue;
            frustumMinZ = maxValue;
            frustumMaxX = -maxValue;
            frustumMaxZ = -maxValue;

            var frustumPoints = camera.getFrustumPoints(camera.farPlane, camera.nearPlane, (this)._frustumPoints);
            intersect(frustumPoints[0], frustumPoints[4]);
            intersect(frustumPoints[1], frustumPoints[5]);
            intersect(frustumPoints[2], frustumPoints[6]);
            intersect(frustumPoints[3], frustumPoints[7]);
            intersect(frustumPoints[0], frustumPoints[3]);
            intersect(frustumPoints[1], frustumPoints[2]);
            intersect(frustumPoints[4], frustumPoints[7]);
            intersect(frustumPoints[5], frustumPoints[6]);

            if ((this).numLines > 0 && frustumMinX < frustumMaxX && frustumMinZ < frustumMaxZ) {
                var halfNumLines = ((this).numLines / 2.0);
                var farPlane = camera.farPlane;
                var metersPerLine = floor(floor(2.0 * farPlane) / floor(halfNumLines));
                if (metersPerLine === 0.0) {
                    metersPerLine = 1;
                }

                var cm = camera.matrix;
                var posX = (floor(cm[9] / metersPerLine) * metersPerLine);
                var posZ = (floor(cm[11] / metersPerLine) * metersPerLine);

                var vp = camera.viewProjectionMatrix;
                var vpRight = md.m44Right(vp);
                var vpAt = md.m44At(vp);
                var vpPos = md.m44Pos(vp);

                var worldRight = md.v4ScalarMul(vpRight, farPlane);
                var worldUp = md.m44Up(vp);
                var worldAt = md.v4ScalarMul(vpAt, farPlane);
                var worldPos = md.v4Add3(md.v4ScalarMul(vpRight, posX), md.v4ScalarMul(vpAt, posZ), vpPos);

                techniqueParameters.worldViewProjection = md.m44Build(worldRight, worldUp, worldAt, worldPos, techniqueParameters.worldViewProjection);

                techniqueParameters.color = (this).color;
                techniqueParameters.fadeToColor = (this).fadeToColor;

                gd.setTechnique(technique);

                gd.setTechniqueParameters(techniqueParameters);

                // Try to draw minimum number of lines
                var invMetersPerLine = 1.0 / metersPerLine;
                var invMaxDistance = 1.0 / farPlane;
                var minX = ((floor(frustumMinX * invMetersPerLine) * metersPerLine) - posX) * invMaxDistance;
                var minZ = ((floor(frustumMinZ * invMetersPerLine) * metersPerLine) - posZ) * invMaxDistance;
                var maxX = ((ceil(frustumMaxX * invMetersPerLine) * metersPerLine) - posX) * invMaxDistance;
                var maxZ = ((ceil(frustumMaxZ * invMetersPerLine) * metersPerLine) - posZ) * invMaxDistance;

                var deltaLine = 2.0 / halfNumLines;
                var maxlinesX = (floor(halfNumLines * (abs(maxZ - minZ) / 2.0)) + 1);
                var maxlinesZ = (floor(halfNumLines * (abs(maxX - minX) / 2.0)) + 1);

                var writer;
                var current;
                var n;

                writer = gd.beginDraw(primitive, ((maxlinesX * 2) + (maxlinesZ * 2)), vertexFormats, semantics);
                if (writer) {
                    current = minZ;
                    for (n = 0; n < maxlinesX; n += 1) {
                        writer(minX, current);
                        writer(maxX, current);
                        current += deltaLine;
                    }

                    current = minX;
                    for (n = 0; n < maxlinesZ; n += 1) {
                        writer(current, minZ);
                        writer(current, maxZ);
                        current += deltaLine;
                    }

                    gd.endDraw(writer);

                    writer = null;
                }
            }
        };

        var shaderParameters = {
            "version": 1,
            "name": "floor.cgfx",
            "parameters": {
                "worldViewProjection": {
                    "type": "float",
                    "rows": 4,
                    "columns": 4
                },
                "color": {
                    "type": "float",
                    "columns": 4
                },
                "fadeToColor": {
                    "type": "float",
                    "columns": 4
                }
            },
            "techniques": {
                "floor": [
                    {
                        "parameters": ["worldViewProjection", "color", "fadeToColor"],
                        "semantics": ["POSITION"],
                        "states": {
                            "DepthTestEnable": true,
                            "DepthFunc": 515,
                            "DepthMask": false,
                            "CullFaceEnable": false,
                            "BlendEnable": false
                        },
                        "programs": ["vp_floor", "fp_floor"]
                    }
                ]
            },
            "programs": {
                "fp_floor": {
                    "type": "fragment",
                    "code": "#ifdef GL_ES\nprecision mediump float;precision mediump int;\n#endif\nvec4 _ret_0;float _TMP11;float _a0012;float _TMP15;float _b0020;uniform vec4 color;uniform vec4 fadeToColor;varying vec4 tz_TexCoord[1];void main()\n{_a0012=dot(tz_TexCoord[0].xy,tz_TexCoord[0].xy);_TMP11=1.0/inversesqrt(_a0012);_b0020=min(1.0,_TMP11);_TMP15=max(0.0,_b0020);_ret_0=color+_TMP15*(fadeToColor-color);gl_FragColor=_ret_0;}"
                },
                "vp_floor": {
                    "type": "vertex",
                    "code": "#ifdef GL_ES\nprecision mediump float;precision mediump int;\n#endif\nvarying vec4 tz_TexCoord[1];attribute vec4 ATTR0;\nvec4 _OUTPosition1;vec2 _OUTDistance1;uniform vec4 worldViewProjection[4];void main()\n{_OUTPosition1=ATTR0.xxxx*worldViewProjection[0]+ATTR0.yyyy*worldViewProjection[2]+worldViewProjection[3];_OUTDistance1=ATTR0.xy;tz_TexCoord[0].xy=ATTR0.xy;gl_Position=_OUTPosition1;}"
                }
            }
        };

        var shader = gd.createShader(shaderParameters);
        if (shader) {
            technique = shader.getTechnique(0);
            return f;
        }

        return null;
    };
    Floor.version = 1;
    return Floor;
})();

Floor.prototype.color = [0.1, 0.1, 1.0, 1.0], Floor.prototype.fadeToColor = [0.95, 0.95, 1.0, 1.0], Floor.prototype.numLines = 200;
Floor.prototype._frustumPoints = [];

// Copyright (c) 2010-2014 Turbulenz Limited
;

//
// Geometry
//
var Geometry = (function () {
    function Geometry() {
        this.semantics = null;
        this.vertexBuffer = null;
        this.vertexOffset = 0;
        this.reference = Reference.create(this);
        this.surfaces = {};
        this.type = "rigid";
        return this;
    }
    Geometry.prototype.destroy = function () {
        if (this.vertexBufferAllocation) {
            this.vertexBufferManager.free(this.vertexBufferAllocation);
            delete this.vertexBufferManager;
            delete this.vertexBufferAllocation;
        }
        if (this.indexBufferAllocation) {
            this.indexBufferManager.free(this.indexBufferAllocation);
            delete this.indexBufferManager;
            delete this.indexBufferAllocation;
        }
        delete this.vertexBuffer;
        delete this.indexBuffer;
        delete this.vertexData;
        delete this.indexData;
        delete this.semantics;
        delete this.first;
        delete this.halfExtents;
        delete this.reference;
        delete this.surfaces;
    };

    Geometry.create = function () {
        return new Geometry();
    };
    Geometry.version = 1;
    return Geometry;
})();

//
// GeometryInstance
//
var GeometryInstance = (function () {
    function GeometryInstance() {
    }
    //
    // clone
    //
    GeometryInstance.prototype.clone = function () {
        var newInstance = GeometryInstance.create(this.geometry, this.surface, this.sharedMaterial);

        if (this.disabled) {
            newInstance.disabled = true;
        }

        return newInstance;
    };

    //
    // isSkinned
    //
    GeometryInstance.prototype.isSkinned = function () {
        if (this.geometry.skeleton) {
            return true;
        }
        return false;
    };

    //
    // setNode
    //
    GeometryInstance.prototype.setNode = function (node) {
        if (this.node) {
            if (this.hasCustomWorldExtents()) {
                this.node.renderableWorldExtentsRemoved();
            }
        }

        this.node = node;

        if (this.node) {
            if (this.hasCustomWorldExtents()) {
                this.node.renderableWorldExtentsUpdated(false);
            }
        }
        this.worldExtentsUpdate = -1;
    };

    //
    // getNode
    //
    GeometryInstance.prototype.getNode = function () {
        return this.node;
    };

    //
    // setMaterial
    //
    GeometryInstance.prototype.setMaterial = function (material) {
        material.reference.add();
        this.sharedMaterial.reference.remove();

        this.sharedMaterial = material;

        this.renderUpdate = undefined;
        this.rendererInfo = undefined;
    };

    //
    // getMaterial
    //
    GeometryInstance.prototype.getMaterial = function () {
        return this.sharedMaterial;
    };

    //
    // getWorldExtents
    //
    GeometryInstance.prototype.getWorldExtents = function () {
        //Note: This method is only valid on a clean node.
        var node = this.node;
        if (node.worldUpdate > this.worldExtentsUpdate) {
            this.worldExtentsUpdate = node.worldUpdate;
            this.updateWorldExtents(node.world);
        }
        return this.worldExtents;
    };

    //
    // updateWorldExtents
    //
    GeometryInstance.prototype.updateWorldExtents = function (world) {
        var center = this.center;
        var halfExtents = this.halfExtents;
        var worldExtents = this.worldExtents;

        var m0 = world[0];
        var m1 = world[1];
        var m2 = world[2];
        var m3 = world[3];
        var m4 = world[4];
        var m5 = world[5];
        var m6 = world[6];
        var m7 = world[7];
        var m8 = world[8];

        var ct0 = world[9];
        var ct1 = world[10];
        var ct2 = world[11];
        if (center) {
            var c0 = center[0];
            var c1 = center[1];
            var c2 = center[2];
            ct0 += (m0 * c0 + m3 * c1 + m6 * c2);
            ct1 += (m1 * c0 + m4 * c1 + m7 * c2);
            ct2 += (m2 * c0 + m5 * c1 + m8 * c2);
        }

        var h0 = halfExtents[0];
        var h1 = halfExtents[1];
        var h2 = halfExtents[2];
        var ht0 = ((m0 < 0 ? -m0 : m0) * h0 + (m3 < 0 ? -m3 : m3) * h1 + (m6 < 0 ? -m6 : m6) * h2);
        var ht1 = ((m1 < 0 ? -m1 : m1) * h0 + (m4 < 0 ? -m4 : m4) * h1 + (m7 < 0 ? -m7 : m7) * h2);
        var ht2 = ((m2 < 0 ? -m2 : m2) * h0 + (m5 < 0 ? -m5 : m5) * h1 + (m8 < 0 ? -m8 : m8) * h2);

        worldExtents[0] = (ct0 - ht0);
        worldExtents[1] = (ct1 - ht1);
        worldExtents[2] = (ct2 - ht2);
        worldExtents[3] = (ct0 + ht0);
        worldExtents[4] = (ct1 + ht1);
        worldExtents[5] = (ct2 + ht2);
    };

    //
    // addCustomWorldExtents
    //
    GeometryInstance.prototype.addCustomWorldExtents = function (customWorldExtents) {
        var alreadyHadCustomExtents = (this.worldExtentsUpdate === GeometryInstance.maxUpdateValue);
        var worldExtents = this.worldExtents;
        if (!alreadyHadCustomExtents || customWorldExtents[0] !== worldExtents[0] || customWorldExtents[1] !== worldExtents[1] || customWorldExtents[2] !== worldExtents[2] || customWorldExtents[3] !== worldExtents[3] || customWorldExtents[4] !== worldExtents[4] || customWorldExtents[5] !== worldExtents[5]) {
            this.worldExtentsUpdate = GeometryInstance.maxUpdateValue;
            worldExtents[0] = customWorldExtents[0];
            worldExtents[1] = customWorldExtents[1];
            worldExtents[2] = customWorldExtents[2];
            worldExtents[3] = customWorldExtents[3];
            worldExtents[4] = customWorldExtents[4];
            worldExtents[5] = customWorldExtents[5];
            this.node.renderableWorldExtentsUpdated(alreadyHadCustomExtents);
        }
    };

    //
    // removeCustomWorldExtents
    //
    GeometryInstance.prototype.removeCustomWorldExtents = function () {
        this.worldExtentsUpdate = -1;
        this.node.renderableWorldExtentsRemoved();
    };

    //
    // getCustomWorldExtents
    //
    GeometryInstance.prototype.getCustomWorldExtents = function () {
        if (this.worldExtentsUpdate === GeometryInstance.maxUpdateValue) {
            return this.worldExtents;
        }
        return undefined;
    };

    //
    // hasCustomWorldExtents
    //
    GeometryInstance.prototype.hasCustomWorldExtents = function () {
        return this.worldExtentsUpdate === GeometryInstance.maxUpdateValue;
    };

    //
    // destroy
    //
    GeometryInstance.prototype.destroy = function () {
        if (this.geometry.reference) {
            this.geometry.reference.remove();
        }

        if (this.sharedMaterial.reference) {
            this.sharedMaterial.reference.remove();
        }

        delete this.surface;
        delete this.geometry;
        delete this.sharedMaterial;
        delete this.techniqueParameters;
        delete this.halfExtents;
        delete this.center;
        delete this.worldExtentsUpdate;
        delete this.drawParameters;
        delete this.renderUpdate;
        delete this.rendererInfo;
    };

    //
    // prepareDrawParameters
    //
    GeometryInstance.prototype.prepareDrawParameters = function (drawParameters) {
        var surface = this.surface;
        var geometry = this.geometry;
        drawParameters.setVertexBuffer(0, geometry.vertexBuffer);
        drawParameters.setSemantics(0, this.semantics);
        drawParameters.setOffset(0, geometry.vertexOffset);

        drawParameters.primitive = surface.primitive;

        drawParameters.firstIndex = surface.first;

        if (surface.indexBuffer) {
            drawParameters.indexBuffer = surface.indexBuffer;
            drawParameters.count = surface.numIndices;
        } else {
            drawParameters.count = surface.numVertices;
        }
    };

    GeometryInstance.create = //
    // Constructor function
    //
    function (geometry, surface, sharedMaterial) {
        var instance = new GeometryInstance();
        var graphicsDevice = TurbulenzEngine.getGraphicsDevice();

        instance.geometry = geometry;
        instance.geometry.reference.add();
        instance.geometryType = geometry.type;
        instance.surface = surface;
        instance.semantics = geometry.semantics;

        instance.halfExtents = geometry.halfExtents;
        instance.center = geometry.center;

        instance.techniqueParameters = graphicsDevice ? graphicsDevice.createTechniqueParameters() : null;
        instance.sharedMaterial = sharedMaterial;
        if (instance.sharedMaterial) {
            instance.sharedMaterial.reference.add();
        }
        instance.worldExtents = new instance.arrayConstructor(6);
        instance.worldExtentsUpdate = -1;

        instance.node = undefined;
        instance.renderUpdate = undefined;
        instance.rendererInfo = undefined;

        return instance;
    };
    GeometryInstance.version = 1;

    GeometryInstance.maxUpdateValue = Number.MAX_VALUE;
    return GeometryInstance;
})();

// Detect correct typed arrays
((function () {
    GeometryInstance.prototype.arrayConstructor = Array;
    if (typeof Float32Array !== "undefined") {
        var testArray = new Float32Array(4);
        var textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            GeometryInstance.prototype.arrayConstructor = Float32Array;
        }
    }
})());

// Copyright (c) 2010-2014 Turbulenz Limited
/*global Reference: false */
//
// Material
//
var Material = (function () {
    function Material() {
    }
    Material.create = function (graphicsDevice) {
        var newMaterial = new Material();
        newMaterial.reference = Reference.create(newMaterial);
        newMaterial.techniqueParameters = graphicsDevice.createTechniqueParameters();
        newMaterial.meta = {};

        newMaterial.onTextureChanged = function materialOnTextureChangedFn(textureInstance) {
            var textureInstanceTexture = textureInstance.texture;
            var material = newMaterial;
            var materialTechniqueParameters = material.techniqueParameters;
            var materialTextureInstances = material.textureInstances;

            for (var p in materialTextureInstances) {
                if (materialTextureInstances.hasOwnProperty(p)) {
                    if (materialTextureInstances[p] === textureInstance) {
                        materialTechniqueParameters[p] = textureInstanceTexture;
                    }
                }
            }
        };

        return newMaterial;
    };

    Material.prototype.getName = function () {
        return this.name;
    };

    Material.prototype.setName = function (name) {
        this.name = name;
    };

    Material.prototype.clone = function (graphicsDevice) {
        var newMaterial = Material.create(graphicsDevice);

        if (this.effect) {
            newMaterial.effect = this.effect;
        }

        if (this.effectName) {
            newMaterial.effectName = this.effectName;
        }

        // Copy meta
        var oldMeta = this.meta;
        var newMeta = newMaterial.meta;
        var p;
        for (p in oldMeta) {
            if (oldMeta.hasOwnProperty(p)) {
                newMeta[p] = oldMeta[p];
            }
        }

        // Copy technique parameters
        var oldTechniqueParameters = this.techniqueParameters;
        var newTechniqueParameters = newMaterial.techniqueParameters;
        for (p in oldTechniqueParameters) {
            if (oldTechniqueParameters.hasOwnProperty(p)) {
                newTechniqueParameters[p] = oldTechniqueParameters[p];
            }
        }

        // Copy texture names
        var oldTextureNames = this.texturesNames;
        if (oldTextureNames) {
            var newTextureNames = newMaterial.texturesNames;
            if (!newTextureNames) {
                newMaterial.texturesNames = newTextureNames = {};
            }

            for (p in oldTextureNames) {
                if (oldTextureNames.hasOwnProperty(p)) {
                    newTextureNames[p] = oldTextureNames[p];
                }
            }
        }

        // Copy texture instances
        var oldTextureInstances = this.textureInstances;
        if (oldTextureInstances) {
            var newTextureInstances = newMaterial.textureInstances;
            if (!newTextureInstances) {
                newMaterial.textureInstances = newTextureInstances = {};
            }

            for (p in oldTextureInstances) {
                if (oldTextureInstances.hasOwnProperty(p)) {
                    var textureInstance = oldTextureInstances[p];
                    newTextureInstances[p] = textureInstance;
                    textureInstance.subscribeTextureChanged(newMaterial.onTextureChanged);
                    textureInstance.reference.add();
                }
            }
        }

        return newMaterial;
    };

    Material.prototype.loadTextures = function (textureManager) {
        var materialTextureNames = this.texturesNames;
        for (var p in materialTextureNames) {
            if (materialTextureNames.hasOwnProperty(p)) {
                var textureName = materialTextureNames[p];
                textureManager.load(textureName);
                this.setTextureInstance(p, textureManager.getInstance(textureName));
            }
        }
    };

    Material.prototype.setTextureInstance = function (propertryName, textureInstance) {
        if (!this.textureInstances) {
            this.textureInstances = {};
        }
        var oldInstance = this.textureInstances[propertryName];
        if (oldInstance !== textureInstance) {
            if (oldInstance && oldInstance.unsubscribeTextureChanged) {
                oldInstance.unsubscribeTextureChanged(this.onTextureChanged);
            }
            this.textureInstances[propertryName] = textureInstance;
            this.techniqueParameters[propertryName] = textureInstance.texture;
            textureInstance.subscribeTextureChanged(this.onTextureChanged);
            textureInstance.reference.add();
        }
    };

    Material.prototype.isSimilar = function (other) {
        if (this.effect !== other.effect) {
            return false;
        }

        if (this.effectName !== other.effectName) {
            return false;
        }

        function similarObjects(a, b) {
            var p;
            for (p in a) {
                if (a.hasOwnProperty(p)) {
                    if (b.hasOwnProperty(p)) {
                        if (a[p] !== b[p]) {
                            return false;
                        }
                    } else {
                        return false;
                    }
                }
            }
            for (p in b) {
                if (b.hasOwnProperty(p)) {
                    if (!a.hasOwnProperty(p)) {
                        return false;
                    }
                }
            }

            return true;
        }

        function similarArrays(a, b) {
            var length = a.length;
            var n;
            for (n = 0; n < length; n += 1) {
                if (a[n] !== b[n]) {
                    return false;
                }
            }
            return true;
        }

        if (this.meta.materialIndex !== other.meta.materialIndex) {
            var atn = this.texturesNames;
            var btn = other.texturesNames;
            if (atn || btn) {
                if (!atn || !btn) {
                    return false;
                }
                if (!similarObjects(atn, btn)) {
                    return false;
                }
            }
        }

        var atp = this.techniqueParameters;
        var btp = other.techniqueParameters;
        var p, av, bv;
        for (p in atp) {
            if (atp.hasOwnProperty(p)) {
                if (btp.hasOwnProperty(p)) {
                    av = atp[p];
                    bv = btp[p];
                    if (av !== bv) {
                        if (av && typeof av !== "number" && bv && typeof bv !== "number" && av.length === bv.length && av.length) {
                            if (!similarArrays(av, bv)) {
                                return false;
                            }
                        } else {
                            return false;
                        }
                    }
                } else {
                    return false;
                }
            }
        }
        for (p in btp) {
            if (btp.hasOwnProperty(p)) {
                if (!atp.hasOwnProperty(p)) {
                    return false;
                }
            }
        }

        return true;
    };

    Material.prototype.destroy = function () {
        delete this.techniqueParameters;

        var textureInstance;
        var textureInstances = this.textureInstances;
        for (var p in textureInstances) {
            if (textureInstances.hasOwnProperty(p)) {
                textureInstance = textureInstances[p];
                textureInstance.unsubscribeTextureChanged(this.onTextureChanged);
                textureInstance.reference.remove();
            }
        }
        delete this.textureInstances;
        delete this.texturesNames;
    };
    Material.version = 1;
    return Material;
})();

// Copyright (c) 2010-2014 Turbulenz Limited
/*global TurbulenzEngine: false */
/*global VMath: false */
//
// Light
//
var Light = (function () {
    function Light() {
    }
    //
    // clone
    //
    Light.prototype.clone = function () {
        var clone = new Light();

        clone.name = this.name;
        clone.spot = this.spot;
        clone.ambient = this.ambient;
        clone.point = this.point;
        clone.fog = this.fog;
        clone.global = this.global;
        clone.directional = this.directional;
        clone.color = (this.color && this.color.slice());
        clone.direction = (this.direction && this.direction.slice());
        clone.origin = (this.origin && this.origin.slice());
        clone.frustum = (this.frustum && this.frustum.slice());
        clone.frustumNear = this.frustumNear;
        clone.center = (this.center && this.center.slice());
        clone.halfExtents = (this.halfExtents && this.halfExtents.slice());
        clone.radius = this.radius;
        clone.shadows = this.shadows;
        clone.dynamicshadows = this.dynamicshadows;
        clone.disabled = this.disabled;
        clone.dynamic = this.dynamic;
        clone.techniqueParameters = this.techniqueParameters;

        return clone;
    };

    //
    // isGlobal
    //
    Light.prototype.isGlobal = function () {
        return this.global;
    };

    Light.create = //
    // Light create
    //
    function (params) {
        var light = new Light();

        var mathDevice = TurbulenzEngine.getMathDevice();

        var abs = Math.abs;
        var max = Math.max;

        if (params.name) {
            light.name = params.name;
        }

        light.color = params.color && params.color.length ? params.color : mathDevice.v3BuildOne();

        if (params.directional) {
            light.directional = true;
        } else if (params.spot) {
            light.spot = true;
        } else if (params.ambient) {
            light.ambient = true;
        } else {
            light.point = true;
        }

        light.origin = params.origin;

        var target = params.target;
        if (target || light.spot) {
            if (!target) {
                target = mathDevice.v3Build(0, 0, -(params.radius || 1));
            }

            // "falloff_angle" is the total angle in degrees
            // calculate half angle in radians: angle * 0.5 / 180 * PI
            var angle = (params.falloff_angle || 90) / 360 * Math.PI;
            var tangent = Math.abs(target[2]) * Math.tan(angle);

            var right = params.right || mathDevice.v3Build(tangent, 0, 0);
            var up = params.up || mathDevice.v3Build(0, tangent, 0);
            var end = params.end || target;

            light.frustum = mathDevice.m33Build(right, up, end);
            var d0 = (abs(right[0]) + abs(up[0]));
            var d1 = (abs(right[1]) + abs(up[1]));
            var d2 = (abs(right[2]) + abs(up[2]));
            var e0 = end[0];
            var e1 = end[1];
            var e2 = end[2];
            var c0, c1, c2;
            var start = params.start;
            if (start) {
                target = mathDevice.v3Normalize(target);
                light.frustumNear = (mathDevice.v3Dot(target, start) / mathDevice.v3Dot(target, end));
                c0 = ((e0 + start[0]) * 0.5);
                c1 = ((e1 + start[1]) * 0.5);
                c2 = ((e2 + start[2]) * 0.5);
            } else {
                light.frustumNear = 0;
                c0 = (e0 * 0.5);
                c1 = (e1 * 0.5);
                c2 = (e2 * 0.5);
            }
            light.center = mathDevice.v3Build(c0, c1, c2);
            light.halfExtents = mathDevice.v3Build(max(abs(e0 - d0 - c0), abs(e0 + d0 - c0)), max(abs(e1 - d1 - c1), abs(e1 + d1 - c1)), max(abs(e2 - d2 - c2), abs(e2 + d2 - c2)));
        } else {
            var halfExtents = params.halfExtents;
            if (halfExtents) {
                light.halfExtents = (halfExtents.length && halfExtents) || mathDevice.v3BuildZero();
            } else {
                var radius = params.radius;
                if (radius) {
                    light.radius = radius;
                    light.halfExtents = mathDevice.v3ScalarBuild(radius);
                } else if (!light.ambient) {
                    light.halfExtents = mathDevice.v3ScalarBuild(VMath.FLOAT_MAX);
                }
            }
        }

        light.direction = params.direction;

        if (!params.halfExtents && !params.radius && !params.target) {
            light.global = true;
        }

        if (!light.global && (params.shadows || params.dynamicshadows)) {
            light.shadows = true;

            if (params.dynamicshadows) {
                light.dynamicshadows = true;
            }
        }

        if (params.disabled) {
            light.disabled = true;
        }

        if (params.dynamic) {
            light.dynamic = true;
        }

        var material = params.material;
        if (material) {
            var techniqueParameters = material.techniqueParameters;

            light.techniqueParameters = techniqueParameters;

            var metaMaterial = material.meta;
            if (metaMaterial) {
                var ambient = metaMaterial.ambient;
                if (ambient) {
                    light.ambient = true;
                }

                var fog = metaMaterial.fog;
                if (fog) {
                    light.fog = true;
                }
            }
        }

        return light;
    };
    Light.version = 1;
    return Light;
})();
;

//
// Light Instance
//
var LightInstance = (function () {
    function LightInstance() {
    }
    //
    // setMaterial
    //
    LightInstance.prototype.setMaterial = function (material) {
        // TODO: this is really being set on the light not the instance so
        // we either need to move the materials and meta to the instance or remove this
        // and create Scene.setLightMaterial
        this.light.sharedMaterial = material;

        var meta = material.meta;
        if (material.meta) {
            var ambient = meta.ambient;
            if (ambient) {
                this.light.ambient = true;
            } else {
                if (this.light.ambient) {
                    delete this.light.ambient;
                }
            }

            var fog = meta.fog;
            if (fog) {
                this.light.fog = true;
            } else {
                if (this.light.fog) {
                    delete this.light.fog;
                }
            }
        }
    };

    //
    // setNode
    //
    LightInstance.prototype.setNode = function (node) {
        this.node = node;
        this.worldExtentsUpdate = -1;
    };

    //
    // getNode
    //
    LightInstance.prototype.getNode = function () {
        return this.node;
    };

    //
    // getWorldExtents
    //
    LightInstance.prototype.getWorldExtents = function () {
        //Note: This method is only valid on a clean node.
        var node = this.node;
        if (node.worldUpdate !== this.worldExtentsUpdate) {
            //Note: set this.worldExtentsUpdate to -1 if local extents change.
            // If we need custom extents we can set worldExtentsUpdate to some distinct value <0.
            this.worldExtentsUpdate = node.worldUpdate;
            this.updateWorldExtents(node.world);
        }
        return this.worldExtents;
    };

    //
    // updateWorldExtents
    //
    LightInstance.prototype.updateWorldExtents = function (world) {
        var worldExtents = this.worldExtents;
        var light = this.light;

        var m0 = world[0];
        var m1 = world[1];
        var m2 = world[2];
        var m3 = world[3];
        var m4 = world[4];
        var m5 = world[5];
        var m6 = world[6];
        var m7 = world[7];
        var m8 = world[8];

        var ct0 = world[9];
        var ct1 = world[10];
        var ct2 = world[11];

        if (light.spot) {
            var minX, minY, minZ, maxX, maxY, maxZ, pX, pY, pZ;
            minX = ct0;
            minY = ct1;
            minZ = ct2;
            maxX = ct0;
            maxY = ct1;
            maxZ = ct2;

            //var transform = md.m33MulM43(light.frustum, world);
            //var p0 = md.m43TransformPoint(transform, md.v3Build(-1, -1, 1));
            //var p1 = md.m43TransformPoint(transform, md.v3Build(1, -1, 1));
            //var p2 = md.m43TransformPoint(transform, md.v3Build(-1, 1, 1));
            //var p3 = md.m43TransformPoint(transform, md.v3Build(1, 1, 1));
            var f = light.frustum;
            var f0 = f[0];
            var f1 = f[1];
            var f2 = f[2];
            var f3 = f[3];
            var f4 = f[4];
            var f5 = f[5];
            var f6 = f[6];
            var f7 = f[7];
            var f8 = f[8];

            ct0 += (m0 * f6 + m3 * f7 + m6 * f8);
            ct1 += (m1 * f6 + m4 * f7 + m7 * f8);
            ct2 += (m2 * f6 + m5 * f7 + m8 * f8);

            var abs = Math.abs;
            var d0 = (abs(m0 * f0 + m3 * f1 + m6 * f2) + abs(m0 * f3 + m3 * f4 + m6 * f5));
            var d1 = (abs(m1 * f0 + m4 * f1 + m7 * f2) + abs(m1 * f3 + m4 * f4 + m7 * f5));
            var d2 = (abs(m2 * f0 + m5 * f1 + m8 * f2) + abs(m2 * f3 + m5 * f4 + m8 * f5));
            pX = (ct0 - d0);
            pY = (ct1 - d1);
            pZ = (ct2 - d2);
            if (minX > pX) {
                minX = pX;
            }
            if (minY > pY) {
                minY = pY;
            }
            if (minZ > pZ) {
                minZ = pZ;
            }

            pX = (ct0 + d0);
            pY = (ct1 + d1);
            pZ = (ct2 + d2);
            if (maxX < pX) {
                maxX = pX;
            }
            if (maxY < pY) {
                maxY = pY;
            }
            if (maxZ < pZ) {
                maxZ = pZ;
            }

            worldExtents[0] = minX;
            worldExtents[1] = minY;
            worldExtents[2] = minZ;
            worldExtents[3] = maxX;
            worldExtents[4] = maxY;
            worldExtents[5] = maxZ;
        } else {
            var center = light.center;
            var halfExtents = light.halfExtents;

            if (center) {
                var c0 = center[0];
                var c1 = center[1];
                var c2 = center[2];
                ct0 += (m0 * c0 + m3 * c1 + m6 * c2);
                ct1 += (m1 * c0 + m4 * c1 + m7 * c2);
                ct2 += (m2 * c0 + m5 * c1 + m8 * c2);
            }

            var h0 = halfExtents[0];
            var h1 = halfExtents[1];
            var h2 = halfExtents[2];
            var ht0 = ((m0 < 0 ? -m0 : m0) * h0 + (m3 < 0 ? -m3 : m3) * h1 + (m6 < 0 ? -m6 : m6) * h2);
            var ht1 = ((m1 < 0 ? -m1 : m1) * h0 + (m4 < 0 ? -m4 : m4) * h1 + (m7 < 0 ? -m7 : m7) * h2);
            var ht2 = ((m2 < 0 ? -m2 : m2) * h0 + (m5 < 0 ? -m5 : m5) * h1 + (m8 < 0 ? -m8 : m8) * h2);

            worldExtents[0] = (ct0 - ht0);
            worldExtents[1] = (ct1 - ht1);
            worldExtents[2] = (ct2 - ht2);
            worldExtents[3] = (ct0 + ht0);
            worldExtents[4] = (ct1 + ht1);
            worldExtents[5] = (ct2 + ht2);
        }
    };

    //
    // clone
    //
    LightInstance.prototype.clone = function () {
        var newInstance = LightInstance.create(this.light);
        return newInstance;
    };

    LightInstance.create = //
    // Constructor function
    //
    function (light) {
        var instance = new LightInstance();

        instance.node = undefined;
        instance.light = light;
        instance.worldExtents = new instance.arrayConstructor(6);
        instance.worldExtentsUpdate = -1;

        return instance;
    };
    LightInstance.version = 1;
    return LightInstance;
})();

// Detect correct typed arrays
((function () {
    LightInstance.prototype.arrayConstructor = Array;
    if (typeof Float32Array !== "undefined") {
        var testArray = new Float32Array(4);
        var textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            LightInstance.prototype.arrayConstructor = Float32Array;
        }
    }
})());

// Copyright (c) 2010-2014 Turbulenz Limited
/*global TurbulenzEngine: false*/
/*global Utilities: false*/
/*global Observer: false*/
;

//
// SceneNode
//
var SceneNode = (function () {
    //
    // SceneNode
    //
    function SceneNode(params) {
        this.name = params.name;

        var md = this.mathDevice;
        if (!md) {
            md = TurbulenzEngine.getMathDevice();
            SceneNode.prototype.mathDevice = md;
        }

        this.dynamic = params.dynamic || false;
        this.disabled = params.disabled || false;

        this.dirtyWorld = false;
        this.dirtyWorldExtents = true;
        this.dirtyLocalExtents = true;
        this.worldUpdate = 0;
        this.frameVisible = -1;

        var local = params.local;
        if (local) {
            if (this.arrayConstructor !== Array) {
                var buffer = new Float32Array(12 + 12);
                this.local = md.m43Copy(local, buffer.subarray(0, 12));
                this.world = md.m43Copy(this.local, buffer.subarray(12, 24));
            } else {
                this.local = md.m43Copy(local);
                this.world = md.m43Copy(this.local);
            }
        } else {
            this.local = undefined;
            this.world = md.m43BuildIdentity();
        }

        this.parent = undefined;
        this.notifiedParent = false;
    }
    SceneNode.makePath = //
    //SceneNode.makePath
    //
    function (parentPath, childName) {
        return parentPath + "/" + childName;
    };

    SceneNode.invalidSetLocalTransform = //
    //SceneNode.invalidSetLocalTransform
    //
    function () {
        /* debug.abort("setLocalTransform can not be called on static nodes."); */
    };

    //
    //getName
    //
    SceneNode.prototype.getName = function () {
        return this.name;
    };

    //
    //getPath
    //
    SceneNode.prototype.getPath = function () {
        if (this.parent) {
            return SceneNode.makePath(this.parent.getPath(), this.name);
        }
        return this.name;
    };

    //
    //getParent
    //
    SceneNode.prototype.getParent = function () {
        return this.parent;
    };

    //
    //setParentHelper
    //
    SceneNode.prototype.setParentHelper = function (parent) {
        //***Only valid to call from addChild()/removeChild() ***
        this.parent = parent;
        this.notifiedParent = false;
        this.dirtyWorld = false;
        this._setDirtyWorldTransform();
    };

    //
    //addChild
    //
    SceneNode.prototype.addChild = function (child) {
        if (child.parent) {
            child.parent.removeChild(child);
        } else {
            if (child.scene) {
                child.scene.removeRootNode(child);
            }
        }

        if (!this.children) {
            this.children = [];
            this.childNeedsUpdateCount = 0;
        }
        this.children.push(child);
        child.setParentHelper(this);

        if (this.dynamic && !child.dynamic) {
            child.setDynamic();
        }
    };

    //
    //removeChild
    //
    SceneNode.prototype.removeChild = function (child) {
        var children = this.children;
        if (children) {
            if (child.notifiedParent) {
                this.childUpdated();
            }
            var numChildren = children.length;
            for (var n = 0; n < numChildren; n += 1) {
                if (children[n] === child) {
                    var root = this.getRoot();
                    if (root.scene) {
                        child.removedFromScene(root.scene);
                    }
                    children.splice(n, 1);
                    child.setParentHelper(null);
                    return;
                }
            }
        }
        /* debug.abort("Invalid child"); */
    };

    //
    //findChild
    //
    SceneNode.prototype.findChild = function (name) {
        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var childIndex = 0; childIndex < numChildren; childIndex += 1) {
                if (children[childIndex].name === name) {
                    return children[childIndex];
                }
            }
        }
        return undefined;
    };

    //
    // clone
    //
    SceneNode.prototype.clone = function (newNodeName) {
        var newNode = SceneNode.create({
            name: newNodeName || this.name,
            local: this.local,
            dynamic: this.dynamic,
            disabled: this.disabled
        });

        // Clone renderables
        var renderables = this.renderables;
        if (renderables) {
            var numRenderables = renderables.length;

            for (var i = 0; i < numRenderables; i += 1) {
                var renderable = renderables[i];
                newNode.addRenderable(renderable.clone());
            }
        }

        // Clone lights
        var lights = this.lightInstances;
        if (lights) {
            var numLights = lights.length;
            for (var l = 0; l < numLights; l += 1) {
                var light = lights[l];
                newNode.addLightInstance(light.clone());
            }
        }

        if (this.clonedObserver) {
            this.clonedObserver.notify({
                oldNode: this,
                newNode: newNode
            });
        }

        var childNodes = this.children;
        if (childNodes) {
            var numChildren = childNodes.length;
            for (var c = 0; c < numChildren; c += 1) {
                newNode.addChild(childNodes[c].clone());
            }
        }

        return newNode;
    };

    //
    //getRoot
    //
    SceneNode.prototype.getRoot = function () {
        var result = this;
        while (result.parent) {
            result = result.parent;
        }
        return result;
    };

    //
    // isInScene
    //
    SceneNode.prototype.isInScene = function () {
        if (this.getRoot().scene) {
            return true;
        }
        return false;
    };

    //
    //removedFromScene
    //
    SceneNode.prototype.removedFromScene = function (scene) {
        if (this.spatialIndex !== undefined) {
            if (this.dynamic) {
                scene.dynamicSpatialMap.remove(this);
            } else {
                scene.staticSpatialMap.remove(this);
                scene.staticNodesChangeCounter += 1;
            }
        }

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var childIndex = 0; childIndex < numChildren; childIndex += 1) {
                children[childIndex].removedFromScene(scene);
            }
        }
    };

    //
    //setLocalTransform
    //
    SceneNode.prototype.setLocalTransform = function (matrix) {
        if (matrix !== this.local) {
            this.local = this.mathDevice.m43Copy(matrix, this.local);
        }

        if (!this.dirtyWorld) {
            this._setDirtyWorldTransform();
        }
    };

    //
    //getLocalTransform
    //
    SceneNode.prototype.getLocalTransform = function () {
        if (!this.local) {
            this.local = this.mathDevice.m43BuildIdentity();
        }
        return this.local;
    };

    //
    //_setDirtyWorldTransform
    //
    SceneNode.prototype._setDirtyWorldTransform = function () {
        //Private function
        //Notify parents
        //inlined updateRequired()
        var parent = this.parent;
        if (parent) {
            if (!this.notifiedParent) {
                this.notifiedParent = true;
                parent.childNeedsUpdate();
            }
        } else {
            //Root nodes
            var scene = this.scene;
            if (scene) {
                scene.addRootNodeToUpdate(this, this.name);
            }
        }

        //Notify children
        var nodes = SceneNode._tempDirtyNodes;
        nodes[0] = this;
        var numRemainingNodes = 1;
        var node, index, child;
        do {
            numRemainingNodes -= 1;
            node = nodes[numRemainingNodes];

            node.dirtyWorld = true;

            if (!node.customWorldExtents && node.localExtents) {
                node.dirtyWorldExtents = true;
            }

            var children = node.children;
            if (children) {
                var numChildren = children.length;

                if (!node.childNeedsUpdateCount) {
                    // Common case of propagating down to clean children
                    node.childNeedsUpdateCount = numChildren;
                    for (index = 0; index < numChildren; index += 1) {
                        child = children[index];
                        child.notifiedParent = true;

                        nodes[numRemainingNodes] = child;
                        numRemainingNodes += 1;
                    }
                } else {
                    for (index = 0; index < numChildren; index += 1) {
                        child = children[index];
                        if (!child.dirtyWorld) {
                            if (!child.notifiedParent) {
                                child.notifiedParent = true;
                                node.childNeedsUpdateCount += 1;
                            }

                            nodes[numRemainingNodes] = child;
                            numRemainingNodes += 1;
                        }
                    }
                }
            }
        } while(0 < numRemainingNodes);
    };

    //
    //getWorldTransform
    //
    SceneNode.prototype.getWorldTransform = function () {
        if (this.dirtyWorld) {
            this.updateWorldTransform();
        }
        return this.world;
    };

    //
    //updateWorldTransform
    //
    SceneNode.prototype.updateWorldTransform = function () {
        if (this.dirtyWorld) {
            this.dirtyWorld = false;
            this.worldUpdate += 1;
            this.checkUpdateRequired();

            var parent = this.parent;
            var local = this.local;
            if (parent) {
                var parentWorld = parent.getWorldTransform();
                if (local) {
                    this.world = this.mathDevice.m43Mul(local, parentWorld, this.world);
                } else {
                    this.world = this.mathDevice.m43Copy(parentWorld, this.world);
                }
            } else {
                this.world = this.mathDevice.m43Copy(local, this.world);
            }
        }
    };

    //
    //setDynamic
    //
    SceneNode.prototype.setDynamic = function () {
        if (!this.dynamic) {
            if (this.spatialIndex !== undefined) {
                var scene = this.getRoot().scene;
                scene.staticSpatialMap.remove(this);
                scene.staticNodesChangeCounter += 1;
                delete this.spatialIndex;
            }
            delete this.setLocalTransform;

            //If there is any dirty state then its possible that even if it still has an spatialIndex it may no longer.
            var worldExtents = this.getWorldExtents();
            if (worldExtents) {
                this.getRoot().scene.dynamicSpatialMap.update(this, worldExtents);
            }
            this.dynamic = true;
        }

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var n = 0; n < numChildren; n += 1) {
                children[n].setDynamic();
            }
        }
    };

    //
    //setStatic
    //
    SceneNode.prototype.setStatic = function () {
        if (this.dynamic) {
            if (this.spatialIndex !== undefined) {
                this.getRoot().scene.dynamicSpatialMap.remove(this);
                delete this.spatialIndex;
            }

            this.setLocalTransform = SceneNode.invalidSetLocalTransform;

            //If there is any dirty state then its possible that even if it still has an spatialIndex it may no longer.
            var worldExtents = this.getWorldExtents();
            if (worldExtents) {
                var scene = this.getRoot().scene;
                if (scene) {
                    scene.staticSpatialMap.update(this, worldExtents);
                    scene.staticNodesChangeCounter += 1;
                }
            }

            delete this.dirtyWorldExtents;
            delete this.worldExtentsUpdate;
            delete this.dirtyWorld;
            delete this.notifiedParent;
            delete this.dynamic;
        }

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var n = 0; n < numChildren; n += 1) {
                children[n].setStatic();
            }
        }
    };

    //
    //setDisabled
    //
    SceneNode.prototype.setDisabled = function (disabled) {
        if (disabled) {
            this.disabled = true;
        } else {
            this.disabled = false;
        }
    };

    //
    //getDisabled
    //
    SceneNode.prototype.getDisabled = function () {
        return this.disabled;
    };

    //
    //enableHierarchy
    //
    SceneNode.prototype.enableHierarchy = function (enabled) {
        this.setDisabled(!enabled);

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var c = 0; c < numChildren; c += 1) {
                children[c].enableHierarchy(enabled);
            }
        }
    };

    //
    //childUpdated
    //
    SceneNode.prototype.childUpdated = function () {
        //Private function
        //debug.assert(this.childNeedsUpdateCount >= 0, "Child update logic incorrect");
        this.childNeedsUpdateCount -= 1;
        if (this.childNeedsUpdateCount === 0 && this.dirtyWorld === false && this.dirtyWorldExtents === false) {
            if (this.parent) {
                this.parent.childUpdated();
                this.notifiedParent = false;
            }
        }
    };

    //
    //childNeedsUpdate
    //
    SceneNode.prototype.childNeedsUpdate = function () {
        //Private function
        this.updateRequired();
        this.childNeedsUpdateCount += 1;
    };

    //
    //updateRequired
    //
    SceneNode.prototype.updateRequired = function () {
        //Private function
        var parent = this.parent;
        if (parent) {
            if (!this.notifiedParent) {
                this.notifiedParent = true;
                parent.childNeedsUpdate();
            }
        } else {
            //Root nodes
            var scene = this.scene;
            if (scene) {
                scene.addRootNodeToUpdate(this, this.name);
            }
        }
    };

    //
    //checkUpdateRequired
    //
    SceneNode.prototype.checkUpdateRequired = function () {
        if (this.notifiedParent) {
            if (!this.dirtyWorldExtents && !this.dirtyWorld && !this.childNeedsUpdateCount) {
                this.parent.childUpdated();
                this.notifiedParent = false;
            }
        }
    };

    //
    //update
    //
    SceneNode.prototype.update = function (scene) {
        var nodes = SceneNode._tempDirtyNodes;
        nodes[0] = this;
        SceneNode.updateNodes(this.mathDevice, (scene || this.scene), nodes, 1);
    };

    SceneNode.updateNodes = function (mathDevice, scene, nodes, numNodes) {
        var dynamicSpatialMap = scene.dynamicSpatialMap;
        var node, parent, index, worldExtents;
        do {
            numNodes -= 1;
            node = nodes[numNodes];

            if (node.dirtyWorld) {
                node.dirtyWorld = false;
                node.worldUpdate += 1;

                parent = node.parent;
                if (parent) {
                    var local = node.local;
                    if (local) {
                        node.world = mathDevice.m43Mul(local, parent.world, node.world);
                    } else {
                        node.world = mathDevice.m43Copy(parent.world, node.world);
                    }
                } else {
                    node.world = mathDevice.m43Copy(node.local, node.world);
                }
            }

            if (node.dirtyWorldExtents) {
                if (node.customWorldExtents) {
                    node.worldExtents = node.customWorldExtents;
                } else {
                    if (node.dirtyLocalExtents) {
                        node.updateLocalExtents();
                    }

                    if (node.numCustomRenderableWorldExtents) {
                        node.updateCustomRenderableWorldExtents();
                    } else if (node.localExtents) {
                        node.recalculateWorldExtents();
                    } else {
                        //no object with size so no extents.
                        delete node.worldExtents;
                    }
                }

                node.dirtyWorldExtents = false;
                node.worldExtentsUpdate = true;
            }

            if (node.worldExtentsUpdate) {
                node.worldExtentsUpdate = false;

                worldExtents = node.worldExtents;
                if (worldExtents) {
                    if (node.dynamic) {
                        dynamicSpatialMap.update(node, worldExtents);
                    } else {
                        scene.staticSpatialMap.update(node, worldExtents);
                        scene.staticNodesChangeCounter += 1;

                        //Remove things that are no longer relevant.
                        node.setLocalTransform = SceneNode.invalidSetLocalTransform;
                        delete node.dirtyWorldExtents;
                        delete node.worldExtentsUpdate;
                        delete node.dirtyWorld;
                        delete node.notifiedParent;
                    }
                } else if (node.spatialIndex !== undefined) {
                    if (node.dynamic) {
                        dynamicSpatialMap.remove(node);
                    } else {
                        scene.staticSpatialMap.remove(node);
                        scene.staticNodesChangeCounter += 1;
                    }
                }
            }

            if (node.childNeedsUpdateCount) {
                node.childNeedsUpdateCount = 0;

                var children = node.children;
                if (children) {
                    var numChildren = children.length;
                    for (index = 0; index < numChildren; index += 1) {
                        var child = children[index];
                        if (child.notifiedParent) {
                            nodes[numNodes] = child;
                            numNodes += 1;
                        }
                    }
                }
            }

            node.notifiedParent = false;
        } while(0 < numNodes);
    };

    //
    //updateLocalExtents
    //
    SceneNode.prototype.updateLocalExtents = function () {
        var localExtents, center, halfExtents;
        var hasExtents = false;
        if (this.customLocalExtents) {
            this.localExtents = this.customLocalExtents;
            hasExtents = true;
        } else {
            var renderables = this.renderables;
            var lights = this.lightInstances;
            var numRenderables = (renderables ? renderables.length : 0);
            var numLights = (lights ? lights.length : 0);
            if (numRenderables || numLights) {
                var maxValue = Number.MAX_VALUE;
                var minValue = -maxValue;
                var min = Math.min;
                var max = Math.max;
                var h0, h1, h2, c0, c1, c2;
                var index;

                var localExtents0 = maxValue;
                var localExtents1 = maxValue;
                var localExtents2 = maxValue;
                var localExtents3 = minValue;
                var localExtents4 = minValue;
                var localExtents5 = minValue;

                for (index = 0; index < numRenderables; index += 1) {
                    var renderable = renderables[index];
                    halfExtents = renderable.halfExtents;
                    if (halfExtents && !renderable.hasCustomWorldExtents()) {
                        h0 = halfExtents[0];
                        h1 = halfExtents[1];
                        h2 = halfExtents[2];

                        center = renderable.center;
                        if (center) {
                            c0 = center[0];
                            c1 = center[1];
                            c2 = center[2];

                            localExtents0 = min(localExtents0, (c0 - h0));
                            localExtents1 = min(localExtents1, (c1 - h1));
                            localExtents2 = min(localExtents2, (c2 - h2));

                            localExtents3 = max(localExtents3, (c0 + h0));
                            localExtents4 = max(localExtents4, (c1 + h1));
                            localExtents5 = max(localExtents5, (c2 + h2));
                        } else {
                            localExtents0 = min(localExtents0, -h0);
                            localExtents1 = min(localExtents1, -h1);
                            localExtents2 = min(localExtents2, -h2);

                            localExtents3 = max(localExtents3, +h0);
                            localExtents4 = max(localExtents4, +h1);
                            localExtents5 = max(localExtents5, +h2);
                        }
                    }
                }

                for (index = 0; index < numLights; index += 1) {
                    var light = lights[index].light;
                    halfExtents = light.halfExtents;
                    if (halfExtents) {
                        h0 = halfExtents[0];
                        h1 = halfExtents[1];
                        h2 = halfExtents[2];

                        center = light.center;
                        if (center) {
                            c0 = center[0];
                            c1 = center[1];
                            c2 = center[2];

                            localExtents0 = min(localExtents0, (c0 - h0));
                            localExtents1 = min(localExtents1, (c1 - h1));
                            localExtents2 = min(localExtents2, (c2 - h2));

                            localExtents3 = max(localExtents3, (c0 + h0));
                            localExtents4 = max(localExtents4, (c1 + h1));
                            localExtents5 = max(localExtents5, (c2 + h2));
                        } else {
                            localExtents0 = min(localExtents0, -h0);
                            localExtents1 = min(localExtents1, -h1);
                            localExtents2 = min(localExtents2, -h2);

                            localExtents3 = max(localExtents3, +h0);
                            localExtents4 = max(localExtents4, +h1);
                            localExtents5 = max(localExtents5, +h2);
                        }
                    }
                }

                if (this.arrayConstructor !== Array) {
                    var bufferSize = 6;
                    if (!this.localHalfExtents) {
                        bufferSize += 3;
                    }
                    if (!this.localExtentsCenter) {
                        bufferSize += 3;
                    }
                    if (!this.worldExtents) {
                        bufferSize += 6;
                    }

                    var buffer = new Float32Array(bufferSize);
                    var bufferIndex = 0;

                    this.localExtents = localExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                    bufferIndex += 6;
                    if (!this.localHalfExtents) {
                        this.localHalfExtents = buffer.subarray(bufferIndex, (bufferIndex + 3));
                        bufferIndex += 3;
                    }
                    if (!this.localExtentsCenter) {
                        this.localExtentsCenter = buffer.subarray(bufferIndex, (bufferIndex + 3));
                        bufferIndex += 3;
                    }
                    if (!this.worldExtents) {
                        this.worldExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                        bufferIndex += 6;
                    }
                } else {
                    this.localExtents = localExtents = new Array(6);
                    if (!this.localHalfExtents) {
                        this.localHalfExtents = new Array(3);
                    }
                    if (!this.localExtentsCenter) {
                        this.localExtentsCenter = new Array(3);
                    }
                    if (!this.worldExtents) {
                        this.worldExtents = new Array(6);
                    }
                }

                localExtents[0] = localExtents0;
                localExtents[1] = localExtents1;
                localExtents[2] = localExtents2;
                localExtents[3] = localExtents3;
                localExtents[4] = localExtents4;
                localExtents[5] = localExtents5;
                hasExtents = true;
            }
        }
        if (hasExtents) {
            localExtents = this.localExtents;

            center = (this.localExtentsCenter || new this.arrayConstructor(3));
            center[0] = (localExtents[3] + localExtents[0]) * 0.5;
            center[1] = (localExtents[4] + localExtents[1]) * 0.5;
            center[2] = (localExtents[5] + localExtents[2]) * 0.5;
            this.localExtentsCenter = center;

            halfExtents = (this.localHalfExtents || new this.arrayConstructor(3));
            halfExtents[0] = (localExtents[3] - center[0]);
            halfExtents[1] = (localExtents[4] - center[1]);
            halfExtents[2] = (localExtents[5] - center[2]);
            this.localHalfExtents = halfExtents;
        } else {
            delete this.localExtents;
            delete this.localExtentsCenter;
            delete this.localHalfExtents;
        }

        this.dirtyLocalExtents = false;
    };

    //
    //getLocalExtents
    //
    SceneNode.prototype.getLocalExtents = function () {
        if (this.dirtyLocalExtents) {
            this.updateLocalExtents();
        }

        //Can be undefined if no local extents. These are not transformed by the local transform matrix.
        return this.localExtents;
    };

    //
    //updateWorldExtents
    //
    SceneNode.prototype.updateWorldExtents = function () {
        if (this.dirtyWorld) {
            this.updateWorldTransform();
        }

        if (this.dirtyWorldExtents) {
            if (this.customWorldExtents) {
                this.worldExtents = this.customWorldExtents;
            } else {
                if (this.dirtyLocalExtents) {
                    this.updateLocalExtents();
                }

                if (this.numCustomRenderableWorldExtents) {
                    this.updateCustomRenderableWorldExtents();
                } else if (this.localExtents) {
                    this.recalculateWorldExtents();
                } else {
                    //no object with size so no extents.
                    delete this.worldExtents;
                }
            }

            this.dirtyWorldExtents = false;
            this.worldExtentsUpdate = true;

            this.checkUpdateRequired();
        }
    };

    //
    //updateCustomRenderableWorldExtents
    //
    SceneNode.prototype.updateCustomRenderableWorldExtents = function () {
        var index, renderable, extents, minX, minY, minZ, maxX, maxY, maxZ;
        var renderables = this.renderables;
        var numRenderables = renderables.length;
        var empty = true;

        for (index = 0; index < numRenderables; index += 1) {
            renderable = renderables[index];
            extents = renderable.getCustomWorldExtents();
            if (extents) {
                minX = extents[0];
                minY = extents[1];
                minZ = extents[2];
                maxX = extents[3];
                maxY = extents[4];
                maxZ = extents[5];
                index += 1;
                empty = false;
                break;
            }
        }

        for (; index < numRenderables; index += 1) {
            renderable = renderables[index];
            extents = renderable.getCustomWorldExtents();
            if (extents) {
                if (minX > extents[0]) {
                    minX = extents[0];
                }
                if (minY > extents[1]) {
                    minY = extents[1];
                }
                if (minZ > extents[2]) {
                    minZ = extents[2];
                }

                if (maxX < extents[3]) {
                    maxX = extents[3];
                }
                if (maxY < extents[4]) {
                    maxY = extents[4];
                }
                if (maxZ < extents[5]) {
                    maxZ = extents[5];
                }
            }
        }

        if (empty) {
            // This should not happen...
            delete this.worldExtents;
        } else {
            var worldExtents = this.worldExtents;
            if (!worldExtents) {
                worldExtents = new this.arrayConstructor(6);
                this.worldExtents = worldExtents;
            }
            worldExtents[0] = minX;
            worldExtents[1] = minY;
            worldExtents[2] = minZ;
            worldExtents[3] = maxX;
            worldExtents[4] = maxY;
            worldExtents[5] = maxZ;
        }
    };

    //
    //recalculateWorldExtents
    //
    SceneNode.prototype.recalculateWorldExtents = function () {
        var localExtentsCenter = this.localExtentsCenter;
        var localHalfExtents = this.localHalfExtents;
        var c0 = localExtentsCenter[0];
        var c1 = localExtentsCenter[1];
        var c2 = localExtentsCenter[2];
        var h0 = localHalfExtents[0];
        var h1 = localHalfExtents[1];
        var h2 = localHalfExtents[2];

        var world = this.world;
        var m0 = world[0];
        var m1 = world[1];
        var m2 = world[2];
        var m3 = world[3];
        var m4 = world[4];
        var m5 = world[5];
        var m6 = world[6];
        var m7 = world[7];
        var m8 = world[8];

        var ct0 = world[9];
        var ct1 = world[10];
        var ct2 = world[11];
        if (c0 !== 0 || c1 !== 0 || c2 !== 0) {
            ct0 += (m0 * c0 + m3 * c1 + m6 * c2);
            ct1 += (m1 * c0 + m4 * c1 + m7 * c2);
            ct2 += (m2 * c0 + m5 * c1 + m8 * c2);
        }

        var ht0 = ((m0 < 0 ? -m0 : m0) * h0 + (m3 < 0 ? -m3 : m3) * h1 + (m6 < 0 ? -m6 : m6) * h2);
        var ht1 = ((m1 < 0 ? -m1 : m1) * h0 + (m4 < 0 ? -m4 : m4) * h1 + (m7 < 0 ? -m7 : m7) * h2);
        var ht2 = ((m2 < 0 ? -m2 : m2) * h0 + (m5 < 0 ? -m5 : m5) * h1 + (m8 < 0 ? -m8 : m8) * h2);

        var worldExtents = this.worldExtents;
        if (!worldExtents) {
            worldExtents = new this.arrayConstructor(6);
            this.worldExtents = worldExtents;
        }
        worldExtents[0] = (ct0 - ht0);
        worldExtents[1] = (ct1 - ht1);
        worldExtents[2] = (ct2 - ht2);
        worldExtents[3] = (ct0 + ht0);
        worldExtents[4] = (ct1 + ht1);
        worldExtents[5] = (ct2 + ht2);
    };

    //
    //getWorldExtents
    //
    SceneNode.prototype.getWorldExtents = function () {
        if (this.dirtyWorldExtents) {
            this.updateWorldExtents();
        }
        return this.worldExtents;
    };

    //
    //addCustomLocalExtents
    //
    SceneNode.prototype.addCustomLocalExtents = function (localExtents) {
        var customLocalExtents = this.customLocalExtents;
        if (!customLocalExtents) {
            this.localExtents = undefined;

            if (this.arrayConstructor !== Array) {
                var bufferSize = 0;
                if (!this.localHalfExtents) {
                    bufferSize += 3;
                }
                if (!this.localExtentsCenter) {
                    bufferSize += 3;
                }
                if (!this.worldExtents) {
                    bufferSize += 6;
                }
                bufferSize += 6;

                var buffer = new Float32Array(bufferSize);
                var bufferIndex = 0;

                if (!this.localHalfExtents) {
                    this.localHalfExtents = buffer.subarray(bufferIndex, (bufferIndex + 3));
                    bufferIndex += 3;
                }
                if (!this.localExtentsCenter) {
                    this.localExtentsCenter = buffer.subarray(bufferIndex, (bufferIndex + 3));
                    bufferIndex += 3;
                }
                if (!this.worldExtents) {
                    this.worldExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                    bufferIndex += 6;
                }
                this.customLocalExtents = customLocalExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                bufferIndex += 6;
            } else {
                if (!this.localHalfExtents) {
                    this.localHalfExtents = new Array(3);
                }
                if (!this.localExtentsCenter) {
                    this.localExtentsCenter = new Array(3);
                }
                if (!this.worldExtents) {
                    this.worldExtents = new Array(6);
                }
                this.customLocalExtents = customLocalExtents = new Array(6);
            }

            customLocalExtents[0] = localExtents[0];
            customLocalExtents[1] = localExtents[1];
            customLocalExtents[2] = localExtents[2];
            customLocalExtents[3] = localExtents[3];
            customLocalExtents[4] = localExtents[4];
            customLocalExtents[5] = localExtents[5];
            this.dirtyLocalExtents = true;
        } else {
            if (customLocalExtents[0] !== localExtents[0] || customLocalExtents[1] !== localExtents[1] || customLocalExtents[2] !== localExtents[2] || customLocalExtents[3] !== localExtents[3] || customLocalExtents[4] !== localExtents[4] || customLocalExtents[5] !== localExtents[5]) {
                customLocalExtents[0] = localExtents[0];
                customLocalExtents[1] = localExtents[1];
                customLocalExtents[2] = localExtents[2];
                customLocalExtents[3] = localExtents[3];
                customLocalExtents[4] = localExtents[4];
                customLocalExtents[5] = localExtents[5];
                this.dirtyLocalExtents = true;
            }
        }
        if (this.dirtyLocalExtents) {
            this.dirtyWorldExtents = true;
            this.updateRequired();
        }
    };

    //
    //removeCustomLocalExtents
    //
    SceneNode.prototype.removeCustomLocalExtents = function () {
        delete this.customLocalExtents;
        this.dirtyWorldExtents = true;
        this.dirtyLocalExtents = true;
        this.updateRequired();
    };

    //
    //getCustomLocalExtents
    //
    SceneNode.prototype.getCustomLocalExtents = function () {
        return this.customLocalExtents;
    };

    //
    //addCustomWorldExtents
    //
    SceneNode.prototype.addCustomWorldExtents = function (worldExtents) {
        var customWorldExtents = this.customWorldExtents;
        if (!customWorldExtents) {
            this.customWorldExtents = customWorldExtents = new this.arrayConstructor(6);
            customWorldExtents[0] = worldExtents[0];
            customWorldExtents[1] = worldExtents[1];
            customWorldExtents[2] = worldExtents[2];
            customWorldExtents[3] = worldExtents[3];
            customWorldExtents[4] = worldExtents[4];
            customWorldExtents[5] = worldExtents[5];
            this.dirtyWorldExtents = true;
        } else {
            if (customWorldExtents[0] !== worldExtents[0] || customWorldExtents[1] !== worldExtents[1] || customWorldExtents[2] !== worldExtents[2] || customWorldExtents[3] !== worldExtents[3] || customWorldExtents[4] !== worldExtents[4] || customWorldExtents[5] !== worldExtents[5]) {
                customWorldExtents[0] = worldExtents[0];
                customWorldExtents[1] = worldExtents[1];
                customWorldExtents[2] = worldExtents[2];
                customWorldExtents[3] = worldExtents[3];
                customWorldExtents[4] = worldExtents[4];
                customWorldExtents[5] = worldExtents[5];
                this.dirtyWorldExtents = true;
            }
        }
        if (this.dirtyWorldExtents) {
            this.updateRequired();
        }
    };

    //
    //removeCustomWorldExtents
    //
    SceneNode.prototype.removeCustomWorldExtents = function () {
        delete this.customWorldExtents;
        this.dirtyWorldExtents = true;
        this.updateRequired();
    };

    //
    //getCustomWorldExtents
    //
    SceneNode.prototype.getCustomWorldExtents = function () {
        return this.customWorldExtents;
    };

    //
    //renderableWorldExtentsUpdated
    //
    SceneNode.prototype.renderableWorldExtentsUpdated = function (wasAlreadyCustom) {
        if (!this.customWorldExtents) {
            this.dirtyWorldExtents = true;
            this.updateRequired();
        }

        if (!wasAlreadyCustom) {
            this.dirtyLocalExtents = true;
            this.numCustomRenderableWorldExtents = (this.numCustomRenderableWorldExtents ? (this.numCustomRenderableWorldExtents + 1) : 1);
        }
    };

    //
    //renderableWorldExtentsRemoved
    //
    SceneNode.prototype.renderableWorldExtentsRemoved = function () {
        if (!this.customWorldExtents) {
            this.dirtyWorldExtents = true;
            this.updateRequired();
        }
        this.dirtyLocalExtents = true;
        this.numCustomRenderableWorldExtents -= 1;
    };

    //
    //calculateHierarchyWorldExtents
    //
    SceneNode.prototype.calculateHierarchyWorldExtents = function (dst) {
        var maxValue = Number.MAX_VALUE;
        var totalExtents = dst;
        if (!totalExtents) {
            totalExtents = new this.arrayConstructor(6);
        }
        totalExtents[0] = maxValue;
        totalExtents[1] = maxValue;
        totalExtents[2] = maxValue;
        totalExtents[3] = -maxValue;
        totalExtents[4] = -maxValue;
        totalExtents[5] = -maxValue;

        if (this._calculateNodeExtents(totalExtents)) {
            return totalExtents;
        } else {
            return undefined;
        }
    };

    SceneNode.prototype._calculateNodeExtents = function (totalExtents) {
        var valid = false;

        var worldExtents = this.getWorldExtents();
        if (worldExtents) {
            totalExtents[0] = (totalExtents[0] < worldExtents[0] ? totalExtents[0] : worldExtents[0]);
            totalExtents[1] = (totalExtents[1] < worldExtents[1] ? totalExtents[1] : worldExtents[1]);
            totalExtents[2] = (totalExtents[2] < worldExtents[2] ? totalExtents[2] : worldExtents[2]);
            totalExtents[3] = (totalExtents[3] > worldExtents[3] ? totalExtents[3] : worldExtents[3]);
            totalExtents[4] = (totalExtents[4] > worldExtents[4] ? totalExtents[4] : worldExtents[4]);
            totalExtents[5] = (totalExtents[5] > worldExtents[5] ? totalExtents[5] : worldExtents[5]);
            valid = true;
        }

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var n = 0; n < numChildren; n += 1) {
                if (children[n]._calculateNodeExtents(totalExtents)) {
                    valid = true;
                }
            }
        }

        return valid;
    };

    //
    //addRenderable
    //
    SceneNode.prototype.addRenderable = function (renderable) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        if (!this.renderables) {
            this.renderables = [];
        }
        this.renderables.push(renderable);
        renderable.setNode(this);
        this.dirtyLocalExtents = true;
    };

    //
    //addRenderableArray
    //
    SceneNode.prototype.addRenderableArray = function (additionalRenderables) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        if (!this.renderables) {
            this.renderables = [];
        }
        var renderables = this.renderables;
        var length = additionalRenderables.length;
        for (var index = 0; index < length; index += 1) {
            renderables.push(additionalRenderables[index]);
            additionalRenderables[index].setNode(this);
        }
        this.dirtyLocalExtents = true;
    };

    //
    //removeRenderable
    //
    SceneNode.prototype.removeRenderable = function (renderable) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        var renderables = this.renderables;
        var numRenderables = renderables.length;
        for (var index = 0; index < numRenderables; index += 1) {
            if (renderables[index] === renderable) {
                renderables[index].setNode(null);
                renderables.splice(index, 1);
                this.dirtyLocalExtents = true;
                return;
            }
        }
        /* debug.abort("Invalid renderable"); */
    };

    //
    //hasRenderables
    //
    SceneNode.prototype.hasRenderables = function () {
        return (this.renderables && this.renderables.length) ? true : false;
    };

    //
    //addLightInstance
    //
    SceneNode.prototype.addLightInstance = function (lightInstance) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        if (!this.lightInstances) {
            this.lightInstances = [];
        }
        this.lightInstances.push(lightInstance);
        lightInstance.setNode(this);
        this.dirtyLocalExtents = true;
    };

    //
    //addLightInstanceArray
    //
    SceneNode.prototype.addLightInstanceArray = function (additionalLightInstances) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        if (!this.lightInstances) {
            this.lightInstances = [];
        }

        var lightInstances = this.lightInstances;
        var length = additionalLightInstances.length;
        for (var index = 0; index < length; index += 1) {
            additionalLightInstances[index].setNode(this);
            lightInstances.push(additionalLightInstances[index]);
        }

        this.dirtyLocalExtents = true;
    };

    //
    //removeLightInstance
    //
    SceneNode.prototype.removeLightInstance = function (lightInstance) {
        this.dirtyWorldExtents = true;
        this.updateRequired();
        var lightInstances = this.lightInstances;
        var numLights = lightInstances.length;
        for (var index = 0; index < numLights; index += 1) {
            if (lightInstances[index] === lightInstance) {
                lightInstance.setNode(null);
                lightInstances.splice(index, 1);
                this.dirtyLocalExtents = true;
                return;
            }
        }
        /* debug.abort("Invalid light"); */
    };

    //
    //hasLightInstances
    //
    SceneNode.prototype.hasLightInstances = function () {
        return (this.lightInstances && this.lightInstances.length);
    };

    //
    //destroy
    //
    SceneNode.prototype.destroy = function () {
        //Should only be called when parent is null
        /* debug.assert(!this.parent, "SceneNode should be remove from parent before destroy is called"); */

        if (this.destroyedObserver) {
            this.destroyedObserver.notify({ node: this });
        }

        var children = this.children;
        if (children) {
            var numChildren = children.length;
            for (var childIndex = numChildren - 1; childIndex >= 0; childIndex -= 1) {
                var child = children[childIndex];
                this.removeChild(child);
                child.destroy();
            }
        }

        var renderables = this.renderables;
        if (renderables) {
            var numRenderables = renderables.length;
            for (var renderableIndex = numRenderables - 1; renderableIndex >= 0; renderableIndex -= 1) {
                var renderable = renderables[renderableIndex];
                if (renderable.destroy) {
                    renderable.destroy();
                }
            }
            this.renderables = [];
        }

        if (this.lightInstances) {
            this.lightInstances = [];
        }

        delete this.scene;

        // Make sure there are no references to any nodes
        var nodes = SceneNode._tempDirtyNodes;
        var numNodes = nodes.length;
        var n;
        for (n = 0; n < numNodes; n += 1) {
            nodes[n] = null;
        }
    };

    //
    //subscribeCloned
    //
    SceneNode.prototype.subscribeCloned = function (observerFunction) {
        if (!this.clonedObserver) {
            this.clonedObserver = Observer.create();
        }
        this.clonedObserver.subscribe(observerFunction);
    };

    //
    //unsubscribeCloned
    //
    SceneNode.prototype.unsubscribeCloned = function (observerFunction) {
        this.clonedObserver.unsubscribe(observerFunction);
    };

    //
    //subscribeDestroyed
    //
    SceneNode.prototype.subscribeDestroyed = function (observerFunction) {
        if (!this.destroyedObserver) {
            this.destroyedObserver = Observer.create();
        }
        this.destroyedObserver.subscribe(observerFunction);
    };

    //
    //unsubscribeDestroyed
    //
    SceneNode.prototype.unsubscribeDestroyed = function (observerFunction) {
        this.destroyedObserver.unsubscribe(observerFunction);
    };

    SceneNode.create = //
    //SceneNode.create
    //
    function (params) {
        return new SceneNode(params);
    };
    SceneNode.version = 1;

    SceneNode._tempDirtyNodes = [];
    return SceneNode;
})();

SceneNode.prototype.mathDevice = null;

// Detect correct typed arrays
((function () {
    SceneNode.prototype.arrayConstructor = Array;
    if (typeof Float32Array !== "undefined") {
        var testArray = new Float32Array(4);
        var textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            SceneNode.prototype.arrayConstructor = Float32Array;
        }
    }
})());

// Copyright (c) 2009-2014 Turbulenz Limited
/*global AABBTree*/
/*global Material*/
/*global SceneNode*/
/*global Geometry*/
/*global GeometryInstance*/
/*global Light*/
/*global LightInstance*/
/*global Utilities*/
/*global VertexBufferManager*/
/*global IndexBufferManager*/
/*global alert*/
/*global Uint16Array*/
/*global Uint32Array*/
/*global Float32Array*/
;

;

;

;

//
// Scene
//
var Scene = (function () {
    // Scene
    function Scene(mathDevice, staticSpatialMap, dynamicSpatialMap) {
        this.md = mathDevice;
        this.staticSpatialMap = (staticSpatialMap || AABBTree.create(true));
        this.dynamicSpatialMap = (dynamicSpatialMap || AABBTree.create());

        this.clear();

        var scene = this;
        this.onGeometryDestroyed = function sceneOnGeometryDestroyedFn(geometry) {
            geometry.reference.unsubscribeDestroyed(scene.onGeometryDestroyed);
            delete scene.shapes[geometry.name];
        };

        this.onMaterialDestroyed = function sceneOnMaterialDestroyedFn(material) {
            material.reference.unsubscribeDestroyed(scene.onMaterialDestroyed);
            delete scene.materials[material.name];
        };
    }
    //
    // findNode
    //
    Scene.prototype.findNode = function (nodePath) {
        //simple case of root node
        var result = this.rootNodesMap[nodePath];
        if (result) {
            return result;
        }

        //else find node in turn
        var names = nodePath.split("/");
        var rootName = names[0];
        result = this.rootNodesMap[rootName];

        for (var depth = 1; result && depth < names.length; depth += 1) {
            result = result.findChild(names[depth]);
        }
        return result;
    };

    //
    // addRootNode
    //
    Scene.prototype.addRootNode = function (rootNode) {
        // Add the root to the top level nodes list and update the scene hierarchys
        var name = rootNode.name;

        /* debug.assert(name, "Root nodes must be named"); */
        /* debug.assert(!rootNode.scene, "Root node already in a scene"); */
        /* debug.assert(!this.rootNodesMap[name], "Root node with the same name exits in the scene"); */

        rootNode.scene = this;

        // Ensure node will be added to spatial map on update
        // In the event that there are no dirty flags set.
        rootNode.worldExtentsUpdate = true;

        this.rootNodes.push(rootNode);
        this.rootNodesMap[name] = rootNode;
        this.addRootNodeToUpdate(rootNode, name);
    };

    //
    // removeRootNode
    //
    Scene.prototype.removeRootNode = function (rootNode) {
        var name = rootNode.name;

        /* debug.assert(rootNode.scene === this, "Root node is not in the scene"); */
        rootNode.removedFromScene(this);

        var rootNodes = this.rootNodes;
        var index = rootNodes.indexOf(rootNode);
        if (index !== -1) {
            var numRootNodes = (rootNodes.length - 1);
            if (index < numRootNodes) {
                rootNodes[index] = rootNodes[numRootNodes];
            }
            rootNodes.length = numRootNodes;
        }
        delete this.rootNodesMap[name];

        if (this.dirtyRoots[name] === rootNode) {
            delete this.dirtyRoots[name];

            // Can not use indexOf because it will search the whole array instead of just the active range
            var nodesToUpdate = this.nodesToUpdate;
            var numNodesToUpdate = this.numNodesToUpdate;
            for (index = 0; index < numNodesToUpdate; index += 1) {
                if (nodesToUpdate[index] === rootNode) {
                    numNodesToUpdate -= 1;
                    if (index < numNodesToUpdate) {
                        nodesToUpdate[index] = nodesToUpdate[numNodesToUpdate];
                    }
                    nodesToUpdate[numNodesToUpdate] = null;
                    this.numNodesToUpdate = numNodesToUpdate;
                    break;
                }
            }
        }

        delete rootNode.scene;
    };

    //
    // addLight
    //
    Scene.prototype.addLight = function (light) {
        this.lights[light.name] = light;

        if (light.isGlobal()) {
            this.globalLights.push(light);
        }
    };

    //
    // removeLight
    //
    Scene.prototype.removeLight = function (light) {
        delete this.lights[light.name];

        if (light.isGlobal()) {
            var globalLights = this.globalLights;
            var numGlobalLights = globalLights.length;
            for (var index = 0; index < numGlobalLights; index += 1) {
                if (light === globalLights[index]) {
                    globalLights.splice(index, 1);
                    break;
                }
            }
        }
    };

    //
    // getLight
    //
    Scene.prototype.getLight = function (name) {
        return this.lights[name];
    };

    //
    // getGlobalLights
    //
    Scene.prototype.getGlobalLights = function () {
        return this.globalLights;
    };

    //
    // calculateNumNodes
    //
    Scene.prototype.calculateNumNodes = function (nodes) {
        var numNodes = nodes.length;
        var numTotalNodes = numNodes;
        for (var n = 0; n < numNodes; n += 1) {
            var children = nodes[n].children;
            if (children) {
                numTotalNodes += this.calculateNumNodes(children);
            }
        }
        return numTotalNodes;
    };

    //
    // buildPortalPlanes
    //
    Scene.prototype.buildPortalPlanes = function (points, planes, cX, cY, cZ, frustumPlanes) {
        var md = this.md;
        var numPoints = points.length;
        var numFrustumPlanes = frustumPlanes.length;
        var numPlanes = 0;
        var n, np, nnp, p, plane, numVisiblePointsPlane;

        /* debug.assert(numFrustumPlanes < 32, "Cannot use bit field for so many planes..."); */

        var culledByPlane = [];
        culledByPlane.length = numPoints;
        np = 0;
        do {
            culledByPlane[np] = 0;
            np += 1;
        } while(np < numPoints);

        n = 0;
        do {
            plane = frustumPlanes[n];
            var pl0 = plane[0];
            var pl1 = plane[1];
            var pl2 = plane[2];
            var pl3 = plane[3];
            numVisiblePointsPlane = 0;

            np = 0;
            do {
                p = points[np];
                if ((pl0 * p[0] + pl1 * p[1] + pl2 * p[2]) >= pl3) {
                    numVisiblePointsPlane += 1;
                } else {
                    /* tslint:disable:no-bitwise */
                    culledByPlane[np] |= (1 << n);
                    /* tslint:enable:no-bitwise */
                }
                np += 1;
            } while(np < numPoints);

            if (numVisiblePointsPlane === 0) {
                planes.length = 0;
                return false;
            } else if (numVisiblePointsPlane < numPoints) {
                planes[numPlanes] = md.v4Copy(plane, planes[numPlanes]);
                numPlanes += 1;
            }
            n += 1;
        } while(n < numFrustumPlanes);

        var allPointsVisible = (numPlanes === 0);

        var newPoints = this.newPoints;
        np = 0;
        do {
            p = points[np];
            newPoints[np] = md.v3Build((p[0] - cX), (p[1] - cY), (p[2] - cZ), newPoints[np]);
            np += 1;
        } while(np < numPoints);

        var sqrt = Math.sqrt;
        np = 0;
        do {
            nnp = (np + 1);
            if (nnp >= numPoints) {
                nnp = 0;
            }

            if (0 !== (culledByPlane[np] & culledByPlane[nnp])) {
                np += 1;
                continue;
            }

            /* tslint:enable:no-bitwise */
            p = newPoints[np];
            var p0X = p[0];
            var p0Y = p[1];
            var p0Z = p[2];

            p = newPoints[nnp];
            var p1X = p[0];
            var p1Y = p[1];
            var p1Z = p[2];

            // n = cross(p0, p1)
            var nX = ((p0Y * p1Z) - (p0Z * p1Y));
            var nY = ((p0Z * p1X) - (p0X * p1Z));
            var nZ = ((p0X * p1Y) - (p0Y * p1X));

            // normalize(n)
            var lnsq = ((nX * nX) + (nY * nY) + (nZ * nZ));
            if (lnsq === 0) {
                planes.length = 0;
                return false;
            }
            var lnrcp = 1.0 / sqrt(lnsq);
            nX *= lnrcp;
            nY *= lnrcp;
            nZ *= lnrcp;

            // d = dot(n, c)
            var d = ((nX * cX) + (nY * cY) + (nZ * cZ));

            planes[numPlanes] = md.v4Build(nX, nY, nZ, d, planes[numPlanes]);
            numPlanes += 1;

            np += 1;
        } while(np < numPoints);

        planes.length = numPlanes;
        return allPointsVisible;
    };

    //
    // findAreaIndex
    //
    Scene.prototype.findAreaIndex = function (bspNodes, cX, cY, cZ) {
        var numNodes = bspNodes.length;
        var nodeIndex = 0;
        var node, plane;
        do {
            node = bspNodes[nodeIndex];
            plane = node.plane;
            nodeIndex = (((plane[0] * cX) + (plane[1] * cY) + (plane[2] * cZ)) < plane[3] ? node.neg : node.pos);
            if (nodeIndex <= 0) {
                return -(nodeIndex + 1);
            }
        } while(nodeIndex < numNodes);
        return -1;
    };

    //
    // findAreaIndicesAABB
    //
    Scene.prototype.findAreaIndicesAABB = function (bspNodes, n0, n1, n2, p0, p1, p2) {
        var numNodes = bspNodes.length;
        var areaIndices = [];
        var visitedArea = [];
        var stack = [0];
        var numNodesStack = 1;
        var nodeIndex, node, plane, areaIndex;
        do {
            numNodesStack -= 1;
            nodeIndex = stack[numNodesStack];
            do {
                node = bspNodes[nodeIndex];
                plane = node.plane;
                var d0 = plane[0];
                var d1 = plane[1];
                var d2 = plane[2];
                var d3 = plane[3];
                if ((d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1) + d2 * (d2 < 0 ? n2 : p2)) < d3) {
                    nodeIndex = node.neg;
                } else {
                    if ((d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1) + d2 * (d2 > 0 ? n2 : p2)) <= d3) {
                        nodeIndex = node.neg;
                        if (nodeIndex <= 0) {
                            if (nodeIndex < 0) {
                                areaIndex = -(nodeIndex + 1);
                                if (!visitedArea[areaIndex]) {
                                    visitedArea[areaIndex] = true;
                                    areaIndices.push(areaIndex);
                                }
                            }
                        } else {
                            stack[numNodesStack] = nodeIndex;
                            numNodesStack += 1;
                        }
                    }
                    nodeIndex = node.pos;
                }
                if (nodeIndex <= 0) {
                    if (nodeIndex < 0) {
                        areaIndex = -(nodeIndex + 1);
                        if (!visitedArea[areaIndex]) {
                            visitedArea[areaIndex] = true;
                            areaIndices.push(areaIndex);
                        }
                    }
                    break;
                }
            } while(nodeIndex < numNodes);
        } while(0 < numNodesStack);
        return areaIndices;
    };

    //
    // findVisiblePortals
    //
    Scene.prototype.findVisiblePortals = function (areaIndex, cX, cY, cZ) {
        var visiblePortals = this.visiblePortals;
        var oldNumVisiblePortals = visiblePortals.length;
        var frustumPlanes = this.frustumPlanes;
        var numFrustumPlanes = frustumPlanes.length;
        var queryCounter = this.getQueryCounter();
        var areas = this.areas;
        var portals, numPortals, portal, plane, area, n, portalPlanes, portalItem;
        var numVisiblePortals = 0;

        // Cull portals behind camera
        // (do NOT use nearPlane directly because areaIndex is based on the camera position)
        var nearPlane = this.nearPlane;
        var nearPlane0 = nearPlane[0];
        var nearPlane1 = nearPlane[1];
        var nearPlane2 = nearPlane[2];
        frustumPlanes[numFrustumPlanes] = this.md.v4Build(nearPlane0, nearPlane1, nearPlane2, ((nearPlane0 * cX) + (nearPlane1 * cY) + (nearPlane2 * cZ)));

        area = areas[areaIndex];
        portals = area.portals;
        numPortals = portals.length;
        for (n = 0; n < numPortals; n += 1) {
            portal = portals[n];
            if (portal.disabled) {
                continue;
            }
            portal.queryCounter = queryCounter;
            plane = portal.plane;
            if (((plane[0] * cX) + (plane[1] * cY) + (plane[2] * cZ)) < plane[3]) {
                if (numVisiblePortals < oldNumVisiblePortals) {
                    portalItem = visiblePortals[numVisiblePortals];
                    portalPlanes = portalItem.planes;
                } else {
                    portalPlanes = [];
                }
                this.buildPortalPlanes(portal.points, portalPlanes, cX, cY, cZ, frustumPlanes);
                if (0 < portalPlanes.length) {
                    if (numVisiblePortals < oldNumVisiblePortals) {
                        portalItem.portal = portal;
                        portalItem.area = portal.area;
                    } else {
                        visiblePortals[numVisiblePortals] = {
                            portal: portal,
                            planes: portalPlanes,
                            area: portal.area
                        };
                    }
                    numVisiblePortals += 1;
                }
            }
        }

        frustumPlanes.length = numFrustumPlanes;

        if (0 < numVisiblePortals) {
            var numPortalPlanes, nextArea, plane0, plane1, plane2, plane3, planes, allPointsVisible;
            var currentPortalIndex = 0;
            do {
                portalItem = visiblePortals[currentPortalIndex];
                currentPortalIndex += 1;
                portalPlanes = portalItem.planes;
                numPortalPlanes = portalPlanes.length;
                portal = portalItem.portal;
                areaIndex = portalItem.area;

                portalPlanes[numPortalPlanes] = portal.plane;

                area = areas[areaIndex];
                portals = area.portals;
                numPortals = portals.length;
                for (n = 0; n < numPortals; n += 1) {
                    portal = portals[n];
                    nextArea = portal.area;
                    if (nextArea !== areaIndex && portal.queryCounter !== queryCounter && !portal.disabled) {
                        plane = portal.plane;
                        plane0 = plane[0];
                        plane1 = plane[1];
                        plane2 = plane[2];
                        plane3 = plane[3];
                        if (((plane0 * cX) + (plane1 * cY) + (plane2 * cZ)) < plane3) {
                            if (numVisiblePortals < oldNumVisiblePortals) {
                                portalItem = visiblePortals[numVisiblePortals];
                                planes = portalItem.planes;
                            } else {
                                planes = [];
                            }
                            allPointsVisible = this.buildPortalPlanes(portal.points, planes, cX, cY, cZ, portalPlanes);
                            if (0 < planes.length) {
                                if (allPointsVisible) {
                                    portal.queryCounter = queryCounter;
                                }
                                if (numVisiblePortals < oldNumVisiblePortals) {
                                    portalItem.portal = portal;
                                    portalItem.area = nextArea;
                                } else {
                                    visiblePortals[numVisiblePortals] = {
                                        portal: portal,
                                        planes: planes,
                                        area: nextArea
                                    };
                                }
                                numVisiblePortals += 1;
                            }
                        } else {
                            portal.queryCounter = queryCounter;
                        }
                    }
                }

                portalPlanes.length = numPortalPlanes;
            } while(currentPortalIndex < numVisiblePortals);
        }

        if (numVisiblePortals < oldNumVisiblePortals) {
            visiblePortals.length = numVisiblePortals;
        }
    };

    //
    // findVisibleNodes
    //
    Scene.prototype.findVisibleNodes = function (camera, visibleNodes) {
        var numVisibleNodes = visibleNodes.length;
        var frustumPlanes = this.frustumPlanes;
        var useSpatialMaps = true;
        var areas = this.areas;
        if (areas) {
            var cameraMatrix = camera.matrix;
            var cX = cameraMatrix[9];
            var cY = cameraMatrix[10];
            var cZ = cameraMatrix[11];

            var areaIndex = this.findAreaIndex(this.bspNodes, cX, cY, cZ);
            this.cameraAreaIndex = areaIndex;

            if (areaIndex >= 0) {
                camera.getFrustumExtents(this.cameraExtents);
                var cameraMinExtent0 = this.cameraExtents[0];
                var cameraMinExtent1 = this.cameraExtents[1];
                var cameraMinExtent2 = this.cameraExtents[2];
                var cameraMaxExtent0 = this.cameraExtents[3];
                var cameraMaxExtent1 = this.cameraExtents[4];
                var cameraMaxExtent2 = this.cameraExtents[5];

                this.findVisiblePortals(areaIndex, cX, cY, cZ);

                var area, na, nodes, numNodes;
                var numAreas = areas.length;
                for (na = 0; na < numAreas; na += 1) {
                    area = areas[na];
                    nodes = area.nodes;
                    numNodes = area.numStaticNodes;
                    if (nodes.length > numNodes) {
                        nodes.length = numNodes;
                    }
                    area.addedDynamicNodes = false;
                }

                var isInsidePlanesAABB = this.isInsidePlanesAABB;
                var dynamicSpatialMap = this.dynamicSpatialMap;
                var visiblePortals = this.visiblePortals;
                var numVisiblePortals = visiblePortals.length;
                var queryCounter = this.getQueryCounter();
                var n, node, np, portalItem, portalPlanes;

                area = areas[areaIndex];
                nodes = area.nodes;
                area.addedDynamicNodes = true;

                var areaExtent = area.extents;
                var areaMinExtent0 = areaExtent[0];
                var areaMinExtent1 = areaExtent[1];
                var areaMinExtent2 = areaExtent[2];
                var areaMaxExtent0 = areaExtent[3];
                var areaMaxExtent1 = areaExtent[4];
                var areaMaxExtent2 = areaExtent[5];
                var combinedExtents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(6) : new Array(6));
                combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
                combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
                combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
                combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
                combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
                combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);

                dynamicSpatialMap.getOverlappingNodes(combinedExtents, nodes);

                numNodes = nodes.length;
                for (n = 0; n < numNodes; n += 1) {
                    node = nodes[n];
                    node.queryCounter = queryCounter;
                    if (isInsidePlanesAABB(node.worldExtents, frustumPlanes)) {
                        visibleNodes[numVisibleNodes] = node;
                        numVisibleNodes += 1;
                    }
                }

                for (np = 0; np < numVisiblePortals; np += 1) {
                    portalItem = visiblePortals[np];
                    portalPlanes = portalItem.planes;
                    area = areas[portalItem.area];
                    nodes = area.nodes;

                    if (!area.addedDynamicNodes) {
                        area.addedDynamicNodes = true;
                        areaExtent = area.extents;
                        areaMinExtent0 = areaExtent[0];
                        areaMinExtent1 = areaExtent[1];
                        areaMinExtent2 = areaExtent[2];
                        areaMaxExtent0 = areaExtent[3];
                        areaMaxExtent1 = areaExtent[4];
                        areaMaxExtent2 = areaExtent[5];
                        combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
                        combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
                        combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
                        combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
                        combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
                        combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);
                        dynamicSpatialMap.getOverlappingNodes(combinedExtents, nodes);
                    }

                    numNodes = nodes.length;
                    for (n = 0; n < numNodes; n += 1) {
                        node = nodes[n];
                        if (node.queryCounter !== queryCounter) {
                            if (isInsidePlanesAABB(node.worldExtents, portalPlanes)) {
                                node.queryCounter = queryCounter;
                                visibleNodes[numVisibleNodes] = node;
                                numVisibleNodes += 1;
                            }
                        }
                    }
                }

                useSpatialMaps = false;
            }
        }

        if (useSpatialMaps) {
            numVisibleNodes += this.staticSpatialMap.getVisibleNodes(frustumPlanes, visibleNodes, numVisibleNodes);
            this.dynamicSpatialMap.getVisibleNodes(frustumPlanes, visibleNodes, numVisibleNodes);
        }
    };

    //
    // findVisibleNodesTree
    //
    Scene.prototype.findVisibleNodesTree = function (tree, camera, visibleNodes) {
        var numVisibleNodes = visibleNodes.length;
        var frustumPlanes = this.frustumPlanes;
        var useSpatialMap = true;
        var areas = this.areas;
        if (areas) {
            // Assume scene.update has been called before this function
            var areaIndex = this.cameraAreaIndex;
            if (areaIndex >= 0) {
                //this.findVisiblePortals(areaIndex, cX, cY, cZ);
                //camera.getFrustumExtents(this.cameraExtents);
                var cameraMinExtent0 = this.cameraExtents[0];
                var cameraMinExtent1 = this.cameraExtents[1];
                var cameraMinExtent2 = this.cameraExtents[2];
                var cameraMaxExtent0 = this.cameraExtents[3];
                var cameraMaxExtent1 = this.cameraExtents[4];
                var cameraMaxExtent2 = this.cameraExtents[5];

                var externalNodesStack = this.externalNodesStack;

                var areaExtent;
                var areaMinExtent0, areaMinExtent1, areaMinExtent2;
                var areaMaxExtent0, areaMaxExtent1, areaMaxExtent2;
                var combinedExtents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(6) : new Array(6));

                var area, na, nodes, numNodes;
                var numAreas = areas.length;
                for (na = 0; na < numAreas; na += 1) {
                    area = areas[na];
                    nodes = area.externalNodes;
                    if (nodes) {
                        externalNodesStack.push(nodes);
                        area.externalNodes = null;
                    }
                }

                var isInsidePlanesAABB = this.isInsidePlanesAABB;
                var findOverlappingAreas = this.findOverlappingAreas;
                var findAreaIndex = this.findAreaIndex;
                var visiblePortals = this.visiblePortals;
                var numVisiblePortals = visiblePortals.length;
                var queryCounter = this.getQueryCounter();
                var bspNodes = this.bspNodes;
                var portalPlanes;
                var n, node, nodeExtents, np, portalItem;
                var cX, cY, cZ, nodeAreaIndex, overlappingAreas, numOverlappingAreas;

                area = areas[areaIndex];
                nodes = area.externalNodes;

                if (!nodes) {
                    if (0 < externalNodesStack.length) {
                        nodes = externalNodesStack.pop();
                    } else {
                        nodes = [];
                    }
                    area.externalNodes = nodes;

                    areaExtent = area.extents;
                    areaMinExtent0 = areaExtent[0];
                    areaMinExtent1 = areaExtent[1];
                    areaMinExtent2 = areaExtent[2];
                    areaMaxExtent0 = areaExtent[3];
                    areaMaxExtent1 = areaExtent[4];
                    areaMaxExtent2 = areaExtent[5];
                    combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
                    combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
                    combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
                    combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
                    combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
                    combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);

                    numNodes = tree.getOverlappingNodes(combinedExtents, nodes, 0);

                    for (n = 0; n < numNodes; n += 1) {
                        node = nodes[n];
                        nodeExtents = node.worldExtents;
                        cX = (nodeExtents[0] + nodeExtents[3]) * 0.5;
                        cY = (nodeExtents[1] + nodeExtents[4]) * 0.5;
                        cZ = (nodeExtents[2] + nodeExtents[5]) * 0.5;
                        nodeAreaIndex = findAreaIndex(bspNodes, cX, cY, cZ);
                        if (nodeAreaIndex >= 0 && areaIndex !== nodeAreaIndex) {
                            overlappingAreas = findOverlappingAreas.call(this, nodeAreaIndex, nodeExtents, true);
                            numOverlappingAreas = overlappingAreas.length;
                            for (na = 0; na < numOverlappingAreas; na += 1) {
                                if (overlappingAreas[na] === area) {
                                    break;
                                }
                            }
                            if (na >= numOverlappingAreas) {
                                numNodes -= 1;
                                if (n < numNodes) {
                                    nodes[n] = nodes[numNodes];
                                    n -= 1;
                                } else {
                                    break;
                                }
                            }
                        }
                    }
                    nodes.length = numNodes;
                }

                numNodes = nodes.length;
                for (n = 0; n < numNodes; n += 1) {
                    node = nodes[n];
                    node.queryCounter = queryCounter;
                    if (isInsidePlanesAABB(node.worldExtents, frustumPlanes)) {
                        visibleNodes[numVisibleNodes] = node;
                        numVisibleNodes += 1;
                    }
                }

                for (np = 0; np < numVisiblePortals; np += 1) {
                    portalItem = visiblePortals[np];
                    portalPlanes = portalItem.planes;
                    areaIndex = portalItem.area;
                    area = areas[areaIndex];
                    nodes = area.externalNodes;

                    if (!nodes) {
                        if (0 < externalNodesStack.length) {
                            nodes = externalNodesStack.pop();
                        } else {
                            nodes = [];
                        }
                        area.externalNodes = nodes;

                        areaExtent = area.extents;
                        areaMinExtent0 = areaExtent[0];
                        areaMinExtent1 = areaExtent[1];
                        areaMinExtent2 = areaExtent[2];
                        areaMaxExtent0 = areaExtent[3];
                        areaMaxExtent1 = areaExtent[4];
                        areaMaxExtent2 = areaExtent[5];
                        combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
                        combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
                        combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
                        combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
                        combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
                        combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);

                        numNodes = tree.getOverlappingNodes(combinedExtents, nodes, 0);

                        for (n = 0; n < numNodes; n += 1) {
                            node = nodes[n];
                            nodeExtents = node.worldExtents;
                            cX = (nodeExtents[0] + nodeExtents[3]) * 0.5;
                            cY = (nodeExtents[1] + nodeExtents[4]) * 0.5;
                            cZ = (nodeExtents[2] + nodeExtents[5]) * 0.5;
                            nodeAreaIndex = findAreaIndex(bspNodes, cX, cY, cZ);
                            if (nodeAreaIndex >= 0 && areaIndex !== nodeAreaIndex) {
                                overlappingAreas = findOverlappingAreas.call(this, nodeAreaIndex, nodeExtents, true);
                                numOverlappingAreas = overlappingAreas.length;
                                for (na = 0; na < numOverlappingAreas; na += 1) {
                                    if (overlappingAreas[na] === area) {
                                        break;
                                    }
                                }
                                if (na >= numOverlappingAreas) {
                                    numNodes -= 1;
                                    if (n < numNodes) {
                                        nodes[n] = nodes[numNodes];
                                        n -= 1;
                                    } else {
                                        break;
                                    }
                                }
                            }
                        }
                        nodes.length = numNodes;
                    }

                    numNodes = nodes.length;
                    for (n = 0; n < numNodes; n += 1) {
                        node = nodes[n];
                        if (node.queryCounter !== queryCounter) {
                            if (isInsidePlanesAABB(node.worldExtents, portalPlanes)) {
                                node.queryCounter = queryCounter;
                                visibleNodes[numVisibleNodes] = node;
                                numVisibleNodes += 1;
                            }
                        }
                    }
                }

                useSpatialMap = false;
            }
        }

        if (useSpatialMap) {
            tree.getVisibleNodes(frustumPlanes, visibleNodes, numVisibleNodes);
        }
    };

    //
    // buildPortalPlanesNoFrustum
    //
    Scene.prototype.buildPortalPlanesNoFrustum = function (points, planes, cX, cY, cZ, parentPlanes) {
        var md = this.md;
        var numPoints = points.length;
        var numParentPlanes = (parentPlanes ? parentPlanes.length : 0);
        var numPlanes = numParentPlanes;
        var newPoints = this.newPoints;
        var np, p;

        np = 0;
        do {
            p = points[np];
            newPoints[np] = md.v3Build((p[0] - cX), (p[1] - cY), (p[2] - cZ), newPoints[np]);
            np += 1;
        } while(np < numPoints);

        var sqrt = Math.sqrt;
        np = 0;
        do {
            p = newPoints[np];
            var p0X = p[0];
            var p0Y = p[1];
            var p0Z = p[2];

            p = newPoints[((np + 1) < numPoints ? (np + 1) : 0)];
            var p1X = p[0];
            var p1Y = p[1];
            var p1Z = p[2];

            // n = cross(p0, p1)
            var nX = ((p0Y * p1Z) - (p0Z * p1Y));
            var nY = ((p0Z * p1X) - (p0X * p1Z));
            var nZ = ((p0X * p1Y) - (p0Y * p1X));
            var lnsq = ((nX * nX) + (nY * nY) + (nZ * nZ));
            if (lnsq === 0) {
                return false;
            }
            var lnrcp = 1.0 / sqrt(lnsq);
            nX *= lnrcp;
            nY *= lnrcp;
            nZ *= lnrcp;

            // d = dot(n, c)
            var d = ((nX * cX) + (nY * cY) + (nZ * cZ));

            planes[numPlanes] = md.v4Build(nX, nY, nZ, d, planes[numPlanes]);
            numPlanes += 1;

            np += 1;
        } while(np < numPoints);

        for (np = 0; np < numParentPlanes; np += 1) {
            planes[np] = md.v4Copy(parentPlanes[np], planes[np]);
        }

        planes.length = numPlanes;
        return true;
    };

    //
    // findOverlappingPortals
    //
    Scene.prototype.findOverlappingPortals = function (areaIndex, cX, cY, cZ, extents, overlappingPortals) {
        var portals, numPortals, n, portal, plane, d0, d1, d2, offset, area, portalExtents, planes;
        var queryCounter = this.getQueryCounter();
        var areas = this.areas;
        var numOverlappingPortals = 0;
        var portalItem;

        var min0 = extents[0];
        var min1 = extents[1];
        var min2 = extents[2];
        var max0 = extents[3];
        var max1 = extents[4];
        var max2 = extents[5];

        area = areas[areaIndex];
        portals = area.portals;
        numPortals = portals.length;
        for (n = 0; n < numPortals; n += 1) {
            portal = portals[n];
            if (portal.disabled) {
                continue;
            }

            portal.queryCounter = queryCounter;

            portalExtents = portal.extents;
            if (portalExtents[0] < max0 && portalExtents[1] < max1 && portalExtents[2] < max2 && portalExtents[3] > min0 && portalExtents[4] > min1 && portalExtents[5] > min2) {
                plane = portal.plane;
                d0 = plane[0];
                d1 = plane[1];
                d2 = plane[2];
                offset = plane[3];
                if (((d0 * cX) + (d1 * cY) + (d2 * cZ)) < offset && (d0 * (d0 < 0 ? min0 : max0) + d1 * (d1 < 0 ? min1 : max1) + d2 * (d2 < 0 ? min2 : max2)) >= offset) {
                    portalItem = overlappingPortals[numOverlappingPortals];
                    if (portalItem) {
                        planes = portalItem.planes;
                    } else {
                        planes = [];
                        overlappingPortals[numOverlappingPortals] = portalItem = {
                            portal: null,
                            planes: planes,
                            area: 0
                        };
                    }
                    if (this.buildPortalPlanesNoFrustum(portal.points, planes, cX, cY, cZ, null)) {
                        portalItem.portal = portal;
                        portalItem.area = portal.area;
                        numOverlappingPortals += 1;
                    }
                }
            }
        }

        if (0 < numOverlappingPortals) {
            var parentPlanes, nextArea;
            var currentPortalIndex = 0;
            do {
                portalItem = overlappingPortals[currentPortalIndex];
                currentPortalIndex += 1;
                parentPlanes = portalItem.planes;
                areaIndex = portalItem.area;
                portal = portalItem.portal;

                area = areas[areaIndex];
                portals = area.portals;
                numPortals = portals.length;
                for (n = 0; n < numPortals; n += 1) {
                    portal = portals[n];
                    nextArea = portal.area;
                    if (nextArea !== areaIndex && portal.queryCounter !== queryCounter && !portal.disabled) {
                        portalExtents = portal.extents;
                        if (portalExtents[0] < max0 && portalExtents[1] < max1 && portalExtents[2] < max2 && portalExtents[3] > min0 && portalExtents[4] > min1 && portalExtents[5] > min2) {
                            plane = portal.plane;
                            d0 = plane[0];
                            d1 = plane[1];
                            d2 = plane[2];
                            offset = plane[3];
                            if (((d0 * cX) + (d1 * cY) + (d2 * cZ)) < offset && (d0 * (d0 < 0 ? min0 : max0) + d1 * (d1 < 0 ? min1 : max1) + d2 * (d2 < 0 ? min2 : max2)) >= offset) {
                                portalItem = overlappingPortals[numOverlappingPortals];
                                if (portalItem) {
                                    planes = portalItem.planes;
                                } else {
                                    planes = [];
                                    overlappingPortals[numOverlappingPortals] = portalItem = {
                                        portal: null,
                                        planes: planes,
                                        area: 0
                                    };
                                }
                                if (this.buildPortalPlanesNoFrustum(portal.points, planes, cX, cY, cZ, parentPlanes)) {
                                    portal.queryCounter = queryCounter;
                                    portalItem.portal = portal;
                                    portalItem.area = nextArea;
                                    numOverlappingPortals += 1;
                                }
                            } else {
                                portal.queryCounter = queryCounter;
                            }
                        } else {
                            portal.queryCounter = queryCounter;
                        }
                    }
                }
            } while(currentPortalIndex < numOverlappingPortals);
        }

        return numOverlappingPortals;
    };

    //
    // findOverlappingNodes
    //
    Scene.prototype.findOverlappingNodes = function (tree, origin, extents, overlappingNodes) {
        var useSpatialMap = true;

        if (this.areas) {
            useSpatialMap = !this._findOverlappingNodesAreas(tree, origin, extents, overlappingNodes);
        }

        if (useSpatialMap) {
            tree.getOverlappingNodes(extents, overlappingNodes);
        }
    };

    //
    // findStaticOverlappingNodes
    //
    Scene.prototype.findStaticOverlappingNodes = function (origin, extents, overlappingNodes) {
        this.findOverlappingNodes(this.staticSpatialMap, origin, extents, overlappingNodes);
    };

    //
    // findDynamicOverlappingNodes
    //
    Scene.prototype.findDynamicOverlappingNodes = function (origin, extents, overlappingNodes) {
        this.findOverlappingNodes(this.dynamicSpatialMap, origin, extents, overlappingNodes);
    };

    //
    // _findOverlappingNodesAreas
    //
    Scene.prototype._findOverlappingNodesAreas = function (tree, origin, extents, overlappingNodes) {
        // Assume scene.update has been called before this function
        var cX = origin[0];
        var cY = origin[1];
        var cZ = origin[2];
        var areaIndex = this.findAreaIndex(this.bspNodes, cX, cY, cZ);
        if (areaIndex < 0) {
            return false;
        }

        var externalNodesStack = this.externalNodesStack;
        var areas = this.areas;

        var na, area, nodes, numNodes;
        var numAreas = areas.length;
        for (na = 0; na < numAreas; na += 1) {
            area = areas[na];
            nodes = area.externalNodes;
            if (nodes) {
                externalNodesStack.push(nodes);
                area.externalNodes = null;
            }
        }

        var minExtent0 = extents[0];
        var minExtent1 = extents[1];
        var minExtent2 = extents[2];
        var maxExtent0 = extents[3];
        var maxExtent1 = extents[4];
        var maxExtent2 = extents[5];

        area = areas[areaIndex];
        var areaExtents = area.extents;
        var testMinExtent0 = areaExtents[0];
        var testMinExtent1 = areaExtents[1];
        var testMinExtent2 = areaExtents[2];
        var testMaxExtent0 = areaExtents[3];
        var testMaxExtent1 = areaExtents[4];
        var testMaxExtent2 = areaExtents[5];

        var overlappingPortals = this.overlappingPortals;
        var numOverlappingPortals = this.findOverlappingPortals(areaIndex, cX, cY, cZ, extents, overlappingPortals);

        var isInsidePlanesAABB = this.isInsidePlanesAABB;
        var queryCounter = this.getQueryCounter();
        var numOverlappingNodes = overlappingNodes.length;
        var portalPlanes;
        var n, node, np, portalItem;

        if (0 < externalNodesStack.length) {
            nodes = externalNodesStack.pop();
        } else {
            nodes = [];
        }
        area.externalNodes = nodes;

        var testExtents = this.testExtents;
        testExtents[0] = (testMinExtent0 > minExtent0 ? testMinExtent0 : minExtent0);
        testExtents[1] = (testMinExtent1 > minExtent1 ? testMinExtent1 : minExtent1);
        testExtents[2] = (testMinExtent2 > minExtent2 ? testMinExtent2 : minExtent2);
        testExtents[3] = (testMaxExtent0 < maxExtent0 ? testMaxExtent0 : maxExtent0);
        testExtents[4] = (testMaxExtent1 < maxExtent1 ? testMaxExtent1 : maxExtent1);
        testExtents[5] = (testMaxExtent2 < maxExtent2 ? testMaxExtent2 : maxExtent2);

        nodes.length = tree.getOverlappingNodes(testExtents, nodes, 0);

        numNodes = nodes.length;
        for (n = 0; n < numNodes; n += 1) {
            node = nodes[n];
            node.queryCounter = queryCounter;
            overlappingNodes[numOverlappingNodes] = node;
            numOverlappingNodes += 1;
        }

        for (np = 0; np < numOverlappingPortals; np += 1) {
            portalItem = overlappingPortals[np];
            portalPlanes = portalItem.planes;
            area = areas[portalItem.area];
            nodes = area.externalNodes;

            if (!nodes) {
                if (0 < externalNodesStack.length) {
                    nodes = externalNodesStack.pop();
                } else {
                    nodes = [];
                }
                area.externalNodes = nodes;
                areaExtents = area.extents;
                testMinExtent0 = areaExtents[0];
                testMinExtent1 = areaExtents[1];
                testMinExtent2 = areaExtents[2];
                testMaxExtent0 = areaExtents[3];
                testMaxExtent1 = areaExtents[4];
                testMaxExtent2 = areaExtents[5];

                testExtents[0] = (testMinExtent0 > minExtent0 ? testMinExtent0 : minExtent0);
                testExtents[1] = (testMinExtent1 > minExtent1 ? testMinExtent1 : minExtent1);
                testExtents[2] = (testMinExtent2 > minExtent2 ? testMinExtent2 : minExtent2);
                testExtents[3] = (testMaxExtent0 < maxExtent0 ? testMaxExtent0 : maxExtent0);
                testExtents[4] = (testMaxExtent1 < maxExtent1 ? testMaxExtent1 : maxExtent1);
                testExtents[5] = (testMaxExtent2 < maxExtent2 ? testMaxExtent2 : maxExtent2);

                nodes.length = tree.getOverlappingNodes(testExtents, nodes, 0);
            }

            numNodes = nodes.length;
            for (n = 0; n < numNodes; n += 1) {
                node = nodes[n];
                if (node.queryCounter !== queryCounter) {
                    if (isInsidePlanesAABB(node.worldExtents, portalPlanes)) {
                        node.queryCounter = queryCounter;
                        overlappingNodes[numOverlappingNodes] = node;
                        numOverlappingNodes += 1;
                    }
                }
            }
        }

        return true;
    };

    //
    // findOverlappingRenderables
    //
    Scene.prototype.findOverlappingRenderables = function (tree, origin, extents, overlappingRenderables) {
        var useSpatialMap = true;

        if (this.areas) {
            useSpatialMap = !this._findOverlappingRenderablesAreas(tree, origin, extents, overlappingRenderables);
        }

        if (useSpatialMap) {
            this._findOverlappingRenderablesNoAreas(tree, extents, overlappingRenderables);
        }
    };

    //
    // findStaticOverlappingRenderables
    //
    Scene.prototype.findStaticOverlappingRenderables = function (origin, extents, overlappingRenderables) {
        this.findOverlappingRenderables(this.staticSpatialMap, origin, extents, overlappingRenderables);
    };

    //
    // findDynamicOverlappingRenderables
    //
    Scene.prototype.findDynamicOverlappingRenderables = function (origin, extents, overlappingRenderables) {
        this.findOverlappingRenderables(this.dynamicSpatialMap, origin, extents, overlappingRenderables);
    };

    //
    // _findOverlappingRenderablesAreas
    //
    Scene.prototype._findOverlappingRenderablesAreas = function (tree, origin, extents, overlappingRenderables) {
        // Assume scene.update has been called before this function
        var cX = origin[0];
        var cY = origin[1];
        var cZ = origin[2];
        var areaIndex = this.findAreaIndex(this.bspNodes, cX, cY, cZ);
        if (areaIndex < 0) {
            return false;
        }

        var numOverlappingRenderables = overlappingRenderables.length;
        var minExtent0 = extents[0];
        var minExtent1 = extents[1];
        var minExtent2 = extents[2];
        var maxExtent0 = extents[3];
        var maxExtent1 = extents[4];
        var maxExtent2 = extents[5];

        var node;
        var numNodes;
        var nodeIndex;
        var renderable;
        var renderables;
        var numRenderables;
        var nodeExtents;
        var renderableIndex;
        var renderableExtents;

        var externalNodesStack = this.externalNodesStack;
        var areas = this.areas;

        var na, area, nodes;
        var numAreas = areas.length;
        for (na = 0; na < numAreas; na += 1) {
            area = areas[na];
            nodes = area.externalNodes;
            if (nodes) {
                externalNodesStack.push(nodes);
                area.externalNodes = null;
            }
        }

        area = areas[areaIndex];
        var areaExtents = area.extents;
        var testMinExtent0 = areaExtents[0];
        var testMinExtent1 = areaExtents[1];
        var testMinExtent2 = areaExtents[2];
        var testMaxExtent0 = areaExtents[3];
        var testMaxExtent1 = areaExtents[4];
        var testMaxExtent2 = areaExtents[5];

        var overlappingPortals = this.overlappingPortals;
        var numOverlappingPortals = this.findOverlappingPortals(areaIndex, cX, cY, cZ, extents, overlappingPortals);

        var isInsidePlanesAABB = this.isInsidePlanesAABB;
        var isFullyInsidePlanesAABB = this.isFullyInsidePlanesAABB;
        var queryCounter = this.getQueryCounter();
        var portalPlanes;
        var n, np, portalItem;
        var allVisible;

        if (0 < externalNodesStack.length) {
            nodes = externalNodesStack.pop();
        } else {
            nodes = [];
        }
        area.externalNodes = nodes;

        var testExtents = this.testExtents;
        testExtents[0] = (testMinExtent0 > minExtent0 ? testMinExtent0 : minExtent0);
        testExtents[1] = (testMinExtent1 > minExtent1 ? testMinExtent1 : minExtent1);
        testExtents[2] = (testMinExtent2 > minExtent2 ? testMinExtent2 : minExtent2);
        testExtents[3] = (testMaxExtent0 < maxExtent0 ? testMaxExtent0 : maxExtent0);
        testExtents[4] = (testMaxExtent1 < maxExtent1 ? testMaxExtent1 : maxExtent1);
        testExtents[5] = (testMaxExtent2 < maxExtent2 ? testMaxExtent2 : maxExtent2);

        nodes.length = tree.getOverlappingNodes(testExtents, nodes, 0);

        numNodes = nodes.length;
        for (nodeIndex = 0; nodeIndex < numNodes; nodeIndex += 1) {
            node = nodes[nodeIndex];
            node.queryCounter = queryCounter;
            renderables = node.renderables;
            if (renderables) {
                numRenderables = renderables.length;
                if (numRenderables === 1) {
                    overlappingRenderables[numOverlappingRenderables] = renderables[0];
                    numOverlappingRenderables += 1;
                } else {
                    // Check if node is fully inside
                    nodeExtents = node.worldExtents;
                    if (nodeExtents[0] >= minExtent0 && nodeExtents[1] >= minExtent1 && nodeExtents[2] >= minExtent2 && nodeExtents[3] <= maxExtent0 && nodeExtents[4] <= maxExtent1 && nodeExtents[5] <= maxExtent2) {
                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                            overlappingRenderables[numOverlappingRenderables] = renderables[renderableIndex];
                            numOverlappingRenderables += 1;
                        }
                    } else {
                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                            renderable = renderables[renderableIndex];
                            renderableExtents = renderable.getWorldExtents();
                            if (renderableExtents[3] >= minExtent0 && renderableExtents[4] >= minExtent1 && renderableExtents[5] >= minExtent2 && renderableExtents[0] <= maxExtent0 && renderableExtents[1] <= maxExtent1 && renderableExtents[2] <= maxExtent2) {
                                overlappingRenderables[numOverlappingRenderables] = renderable;
                                numOverlappingRenderables += 1;
                            }
                        }
                    }
                }
            }
        }

        for (np = 0; np < numOverlappingPortals; np += 1) {
            portalItem = overlappingPortals[np];
            portalPlanes = portalItem.planes;
            area = areas[portalItem.area];
            nodes = area.externalNodes;

            if (!nodes) {
                if (0 < externalNodesStack.length) {
                    nodes = externalNodesStack.pop();
                } else {
                    nodes = [];
                }
                area.externalNodes = nodes;
                areaExtents = area.extents;
                testMinExtent0 = areaExtents[0];
                testMinExtent1 = areaExtents[1];
                testMinExtent2 = areaExtents[2];
                testMaxExtent0 = areaExtents[3];
                testMaxExtent1 = areaExtents[4];
                testMaxExtent2 = areaExtents[5];

                testExtents[0] = (testMinExtent0 > minExtent0 ? testMinExtent0 : minExtent0);
                testExtents[1] = (testMinExtent1 > minExtent1 ? testMinExtent1 : minExtent1);
                testExtents[2] = (testMinExtent2 > minExtent2 ? testMinExtent2 : minExtent2);
                testExtents[3] = (testMaxExtent0 < maxExtent0 ? testMaxExtent0 : maxExtent0);
                testExtents[4] = (testMaxExtent1 < maxExtent1 ? testMaxExtent1 : maxExtent1);
                testExtents[5] = (testMaxExtent2 < maxExtent2 ? testMaxExtent2 : maxExtent2);

                nodes.length = tree.getOverlappingNodes(testExtents, nodes, 0);
            }

            numNodes = nodes.length;
            for (n = 0; n < numNodes; n += 1) {
                node = nodes[n];
                if (node.queryCounter !== queryCounter) {
                    allVisible = true;

                    renderables = node.renderables;
                    if (renderables) {
                        nodeExtents = node.worldExtents;
                        if (isInsidePlanesAABB(nodeExtents, portalPlanes)) {
                            numRenderables = renderables.length;
                            if (numRenderables === 1) {
                                renderable = renderables[0];
                                if (renderable.queryCounter !== queryCounter) {
                                    renderable.queryCounter = queryCounter;
                                    overlappingRenderables[numOverlappingRenderables] = renderable;
                                    numOverlappingRenderables += 1;
                                }
                            } else {
                                if (nodeExtents[0] >= minExtent0 && nodeExtents[1] >= minExtent1 && nodeExtents[2] >= minExtent2 && nodeExtents[3] <= maxExtent0 && nodeExtents[4] <= maxExtent1 && nodeExtents[5] <= maxExtent2) {
                                    if (isFullyInsidePlanesAABB(nodeExtents, portalPlanes)) {
                                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                                            renderable = renderables[renderableIndex];
                                            if (renderable.queryCounter !== queryCounter) {
                                                renderable.queryCounter = queryCounter;
                                                overlappingRenderables[numOverlappingRenderables] = renderable;
                                                numOverlappingRenderables += 1;
                                            }
                                        }
                                    } else {
                                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                                            renderable = renderables[renderableIndex];
                                            if (renderable.queryCounter !== queryCounter) {
                                                if (isInsidePlanesAABB(renderable.getWorldExtents(), portalPlanes)) {
                                                    renderable.queryCounter = queryCounter;
                                                    overlappingRenderables[numOverlappingRenderables] = renderable;
                                                    numOverlappingRenderables += 1;
                                                } else {
                                                    allVisible = false;
                                                }
                                            }
                                        }
                                    }
                                } else {
                                    if (isFullyInsidePlanesAABB(nodeExtents, portalPlanes)) {
                                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                                            renderable = renderables[renderableIndex];
                                            if (renderable.queryCounter !== queryCounter) {
                                                renderableExtents = renderable.getWorldExtents();
                                                if (renderableExtents[3] >= minExtent0 && renderableExtents[4] >= minExtent1 && renderableExtents[5] >= minExtent2 && renderableExtents[0] <= maxExtent0 && renderableExtents[1] <= maxExtent1 && renderableExtents[2] <= maxExtent2) {
                                                    renderable.queryCounter = queryCounter;
                                                    overlappingRenderables[numOverlappingRenderables] = renderable;
                                                    numOverlappingRenderables += 1;
                                                } else {
                                                    allVisible = false;
                                                }
                                            }
                                        }
                                    } else {
                                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                                            renderable = renderables[renderableIndex];
                                            if (renderable.queryCounter !== queryCounter) {
                                                renderableExtents = renderable.getWorldExtents();
                                                if (renderableExtents[3] >= minExtent0 && renderableExtents[4] >= minExtent1 && renderableExtents[5] >= minExtent2 && renderableExtents[0] <= maxExtent0 && renderableExtents[1] <= maxExtent1 && renderableExtents[2] <= maxExtent2 && isInsidePlanesAABB(renderableExtents, portalPlanes)) {
                                                    renderable.queryCounter = queryCounter;
                                                    overlappingRenderables[numOverlappingRenderables] = renderable;
                                                    numOverlappingRenderables += 1;
                                                } else {
                                                    allVisible = false;
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        } else {
                            allVisible = false;
                        }
                    }

                    if (allVisible) {
                        node.queryCounter = queryCounter;
                    }
                }
            }
        }

        return true;
    };

    //
    // _findOverlappingRenderablesNoAreas
    //
    Scene.prototype._findOverlappingRenderablesNoAreas = function (tree, extents, overlappingRenderables) {
        var numOverlappingRenderables = overlappingRenderables.length;
        var minExtent0 = extents[0];
        var minExtent1 = extents[1];
        var minExtent2 = extents[2];
        var maxExtent0 = extents[3];
        var maxExtent1 = extents[4];
        var maxExtent2 = extents[5];

        var overlappingNodes = this.queryVisibleNodes;

        var node;
        var numNodes;
        var nodeIndex;
        var renderable;
        var renderables;
        var numRenderables;
        var nodeExtents;
        var renderableIndex;
        var renderableExtents;

        numNodes = tree.getOverlappingNodes(extents, overlappingNodes, 0);
        for (nodeIndex = 0; nodeIndex < numNodes; nodeIndex += 1) {
            node = overlappingNodes[nodeIndex];
            renderables = node.renderables;
            if (renderables) {
                numRenderables = renderables.length;
                if (numRenderables === 1) {
                    overlappingRenderables[numOverlappingRenderables] = renderables[0];
                    numOverlappingRenderables += 1;
                } else {
                    // Check if node is fully inside
                    nodeExtents = node.worldExtents;
                    if (nodeExtents[0] >= minExtent0 && nodeExtents[1] >= minExtent1 && nodeExtents[2] >= minExtent2 && nodeExtents[3] <= maxExtent0 && nodeExtents[4] <= maxExtent1 && nodeExtents[5] <= maxExtent2) {
                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                            overlappingRenderables[numOverlappingRenderables] = renderables[renderableIndex];
                            numOverlappingRenderables += 1;
                        }
                    } else {
                        for (renderableIndex = 0; renderableIndex < numRenderables; renderableIndex += 1) {
                            renderable = renderables[renderableIndex];
                            renderableExtents = renderable.getWorldExtents();
                            if (renderableExtents[3] >= minExtent0 && renderableExtents[4] >= minExtent1 && renderableExtents[5] >= minExtent2 && renderableExtents[0] <= maxExtent0 && renderableExtents[1] <= maxExtent1 && renderableExtents[2] <= maxExtent2) {
                                overlappingRenderables[numOverlappingRenderables] = renderable;
                                numOverlappingRenderables += 1;
                            }
                        }
                    }
                }
            }
        }
    };

    //
    // cloneRootNode
    //
    Scene.prototype.cloneRootNode = function (rootNode, newInstanceName) {
        var newNode = rootNode.clone(newInstanceName);
        this.addRootNode(newNode);
        return newNode;
    };

    //
    // updateVisibleNodes
    //
    Scene.prototype.updateVisibleNodes = function (camera) {
        var useSpatialMap = true;

        if (this.areas) {
            useSpatialMap = !this._updateVisibleNodesAreas(camera);
        }

        if (useSpatialMap) {
            this._updateVisibleNodesNoAreas(camera);
        }

        this.frameIndex += 1;
    };

    //
    // _updateVisibleNodesNoAreas
    //
    Scene.prototype._updateVisibleNodesNoAreas = function (camera) {
        var visibleNodes = this.visibleNodes;
        var numVisibleNodes = 0;

        var visibleRenderables = this.visibleRenderables;
        var numVisibleRenderables = 0;

        var visibleLights = this.visibleLights;
        var numVisibleLights = 0;

        this.extractFrustumPlanes(camera);
        var frustumPlanes = this.frustumPlanes;

        var frameIndex = this.frameIndex;
        var nearPlane = this.nearPlane;
        var d0 = nearPlane[0];
        var d1 = nearPlane[1];
        var d2 = nearPlane[2];
        var offset = nearPlane[3];
        var maxDistance = 0;
        var n, node;

        var isFullyInsidePlanesAABB = this.isFullyInsidePlanesAABB;
        var isInsidePlanesAABB = this.isInsidePlanesAABB;

        var queryVisibleNodes = this.queryVisibleNodes;
        var numQueryVisibleNodes = this.staticSpatialMap.getVisibleNodes(frustumPlanes, queryVisibleNodes, 0);
        numQueryVisibleNodes += this.dynamicSpatialMap.getVisibleNodes(frustumPlanes, queryVisibleNodes, numQueryVisibleNodes);

        for (n = 0; n < numQueryVisibleNodes; n += 1) {
            node = queryVisibleNodes[n];
            if (!node.disabled) {
                var extents = node.worldExtents;
                var distance, renderable, i, lightInstance, l;

                /* debug.assert(node.frameVisible !== frameIndex); */
                node.frameVisible = frameIndex;

                distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                node.distance = distance;

                if (0 < distance) {
                    //This signifies any part of the node is visible, but not necessarily all.
                    visibleNodes[numVisibleNodes] = node;
                    numVisibleNodes += 1;

                    var renderables = node.renderables;
                    var numRenderables = (renderables ? renderables.length : 0);

                    var lights = node.lightInstances;
                    var numLights = (lights ? lights.length : 0);

                    var fullyVisible = (1 < (numLights + numRenderables) ? isFullyInsidePlanesAABB(extents, frustumPlanes) : false);

                    if (renderables) {
                        if (numRenderables === 1 && !lights) {
                            renderable = renderables[0];
                            if (!renderable.disabled) {
                                if (maxDistance < distance) {
                                    maxDistance = distance;
                                }
                                renderable.distance = distance;
                                renderable.frameVisible = frameIndex;
                                visibleRenderables[numVisibleRenderables] = renderable;
                                numVisibleRenderables += 1;
                            }
                        } else {
                            for (i = 0; i < numRenderables; i += 1) {
                                renderable = renderables[i];
                                if (!renderable.disabled) {
                                    extents = renderable.getWorldExtents();
                                    if (fullyVisible || isInsidePlanesAABB(extents, frustumPlanes)) {
                                        distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                                        if (0 < distance) {
                                            if (maxDistance < distance) {
                                                maxDistance = distance;
                                            }
                                            renderable.distance = distance;
                                            renderable.frameVisible = frameIndex;
                                            visibleRenderables[numVisibleRenderables] = renderable;
                                            numVisibleRenderables += 1;
                                        }
                                    }
                                }
                            }
                        }
                    }

                    if (lights) {
                        if (numLights === 1 && !renderables) {
                            lightInstance = lights[0];
                            if (!lightInstance.disabled && !lightInstance.light.isGlobal()) {
                                lightInstance.distance = distance;
                                lightInstance.frameVisible = frameIndex;
                                visibleLights[numVisibleLights] = lightInstance;
                                numVisibleLights += 1;
                            }
                        } else {
                            for (l = 0; l < numLights; l += 1) {
                                lightInstance = lights[l];
                                if (!lightInstance.disabled && !lightInstance.light.isGlobal()) {
                                    extents = lightInstance.getWorldExtents();
                                    if (fullyVisible || isInsidePlanesAABB(extents, frustumPlanes)) {
                                        distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                                        if (0 < distance) {
                                            lightInstance.distance = distance;
                                            lightInstance.frameVisible = frameIndex;
                                            visibleLights[numVisibleLights] = lightInstance;
                                            numVisibleLights += 1;
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }

        this.maxDistance = (maxDistance + camera.nearPlane);
        if (this.maxDistance < camera.farPlane) {
            this._filterVisibleNodesForCameraBox(camera, numVisibleNodes, numVisibleRenderables, numVisibleLights);
        } else {
            visibleRenderables.length = numVisibleRenderables;
            visibleLights.length = numVisibleLights;
            visibleNodes.length = numVisibleNodes;
        }
    };

    //
    // _updateVisibleNodesAreas
    //
    Scene.prototype._updateVisibleNodesAreas = function (camera) {
        var cameraMatrix = camera.matrix;
        var cX = cameraMatrix[9];
        var cY = cameraMatrix[10];
        var cZ = cameraMatrix[11];

        var areaIndex = this.findAreaIndex(this.bspNodes, cX, cY, cZ);
        this.cameraAreaIndex = areaIndex;

        if (areaIndex < 0) {
            return false;
        }

        var visibleNodes = this.visibleNodes;
        var numVisibleNodes = 0;

        var visibleRenderables = this.visibleRenderables;
        var numVisibleRenderables = 0;

        var visibleLights = this.visibleLights;
        var numVisibleLights = 0;

        this.extractFrustumPlanes(camera);
        var frustumPlanes = this.frustumPlanes;

        var frameIndex = this.frameIndex;
        var nearPlane = this.nearPlane;
        var d0 = nearPlane[0];
        var d1 = nearPlane[1];
        var d2 = nearPlane[2];
        var offset = nearPlane[3];
        var maxDistance = 0;
        var n = 0;
        var node;

        var isFullyInsidePlanesAABB = this.isFullyInsidePlanesAABB;
        var isInsidePlanesAABB = this.isInsidePlanesAABB;

        // findVisibleNodes
        var cameraExtents = this.cameraExtents;

        camera.getFrustumExtents(cameraExtents);

        var cameraMinExtent0 = cameraExtents[0];
        var cameraMinExtent1 = cameraExtents[1];
        var cameraMinExtent2 = cameraExtents[2];
        var cameraMaxExtent0 = cameraExtents[3];
        var cameraMaxExtent1 = cameraExtents[4];
        var cameraMaxExtent2 = cameraExtents[5];

        var areas = this.areas;
        var queryCounter = this.getQueryCounter();

        //
        // sceneProcessVisibleNodeFn helper
        //
        function sceneProcessVisibleNode(node, planes) {
            var extents = node.worldExtents;
            var allVisible = true;
            var distance;

            if (node.frameVisible !== frameIndex) {
                node.frameVisible = frameIndex;

                distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                node.distance = distance;
                if (0 < distance) {
                    //This signifies any part of the node is visible, but not necessarily all.
                    visibleNodes[numVisibleNodes] = node;
                    numVisibleNodes += 1;
                }
            } else {
                distance = node.distance;
            }

            if (0 < distance) {
                var renderable, i, lightInstance, l;
                var renderables = node.renderables;
                var numRenderables = (renderables ? renderables.length : 0);

                var lights = node.lightInstances;
                var numLights = (lights ? lights.length : 0);

                var fullyVisible = (1 < (numLights + numRenderables) ? isFullyInsidePlanesAABB(extents, planes) : false);

                if (renderables) {
                    if (numRenderables === 1 && !lights) {
                        renderable = renderables[0];
                        if (!renderable.disabled && renderable.queryCounter !== queryCounter) {
                            if (maxDistance < distance) {
                                maxDistance = distance;
                            }
                            renderable.distance = distance;
                            renderable.frameVisible = frameIndex;
                            renderable.queryCounter = queryCounter;
                            visibleRenderables[numVisibleRenderables] = renderable;
                            numVisibleRenderables += 1;
                        }
                    } else {
                        for (i = 0; i < numRenderables; i += 1) {
                            renderable = renderables[i];
                            if (!renderable.disabled && renderable.queryCounter !== queryCounter) {
                                extents = renderable.getWorldExtents();
                                if (fullyVisible || isInsidePlanesAABB(extents, planes)) {
                                    distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                                    if (0 < distance) {
                                        if (maxDistance < distance) {
                                            maxDistance = distance;
                                        }
                                        renderable.distance = distance;
                                        renderable.frameVisible = frameIndex;
                                        renderable.queryCounter = queryCounter;
                                        visibleRenderables[numVisibleRenderables] = renderable;
                                        numVisibleRenderables += 1;
                                    } else {
                                        allVisible = false;
                                    }
                                } else {
                                    allVisible = false;
                                }
                            }
                        }
                    }
                }

                if (lights) {
                    if (numLights === 1 && !renderables) {
                        lightInstance = lights[0];
                        if (!lightInstance.disabled && lightInstance.queryCounter !== queryCounter && !lightInstance.light.isGlobal()) {
                            lightInstance.distance = distance;
                            lightInstance.frameVisible = frameIndex;
                            lightInstance.queryCounter = queryCounter;
                            visibleLights[numVisibleLights] = lightInstance;
                            numVisibleLights += 1;
                        }
                    } else {
                        for (l = 0; l < numLights; l += 1) {
                            lightInstance = lights[l];
                            if (!lightInstance.disabled && lightInstance.queryCounter !== queryCounter && !lightInstance.light.isGlobal()) {
                                extents = lightInstance.getWorldExtents();
                                if (fullyVisible || isInsidePlanesAABB(extents, planes)) {
                                    distance = ((d0 * (d0 > 0 ? extents[3] : extents[0])) + (d1 * (d1 > 0 ? extents[4] : extents[1])) + (d2 * (d2 > 0 ? extents[5] : extents[2])) - offset);
                                    if (0 < distance) {
                                        lightInstance.distance = distance;
                                        lightInstance.frameVisible = frameIndex;
                                        lightInstance.queryCounter = queryCounter;
                                        visibleLights[numVisibleLights] = lightInstance;
                                        numVisibleLights += 1;
                                    } else {
                                        allVisible = false;
                                    }
                                } else {
                                    allVisible = false;
                                }
                            }
                        }
                    }
                }
            }

            if (allVisible) {
                node.queryCounter = queryCounter;
            }
        }

        this.findVisiblePortals(areaIndex, cX, cY, cZ);

        var area, na, nodes, numNodes;
        var numAreas = areas.length;
        for (na = 0; na < numAreas; na += 1) {
            area = areas[na];
            nodes = area.nodes;
            numNodes = area.numStaticNodes;
            if (nodes.length > numNodes) {
                nodes.length = numNodes;
            }
            area.addedDynamicNodes = false;
        }

        var dynamicSpatialMap = this.dynamicSpatialMap;
        var visiblePortals = this.visiblePortals;
        var numVisiblePortals = visiblePortals.length;

        var np, portalItem, portalPlanes;

        area = areas[areaIndex];
        nodes = area.nodes;
        area.addedDynamicNodes = true;

        var areaExtent = area.extents;
        var areaMinExtent0 = areaExtent[0];
        var areaMinExtent1 = areaExtent[1];
        var areaMinExtent2 = areaExtent[2];
        var areaMaxExtent0 = areaExtent[3];
        var areaMaxExtent1 = areaExtent[4];
        var areaMaxExtent2 = areaExtent[5];
        var combinedExtents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(6) : new Array(6));
        combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
        combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
        combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
        combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
        combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
        combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);

        dynamicSpatialMap.getOverlappingNodes(combinedExtents, nodes);

        numNodes = nodes.length;
        for (n = 0; n < numNodes; n += 1) {
            node = nodes[n];
            node.queryCounter = queryCounter;
            if (!node.disabled && isInsidePlanesAABB(node.worldExtents, frustumPlanes)) {
                sceneProcessVisibleNode(node, frustumPlanes);
            }
        }

        for (np = 0; np < numVisiblePortals; np += 1) {
            portalItem = visiblePortals[np];
            portalPlanes = portalItem.planes;
            area = areas[portalItem.area];
            nodes = area.nodes;

            // Frustum tests do return some false positives, check bounding boxes
            areaExtent = area.extents;
            areaMinExtent0 = areaExtent[0];
            areaMinExtent1 = areaExtent[1];
            areaMinExtent2 = areaExtent[2];
            areaMaxExtent0 = areaExtent[3];
            areaMaxExtent1 = areaExtent[4];
            areaMaxExtent2 = areaExtent[5];
            if (cameraMaxExtent0 > areaMinExtent0 && cameraMaxExtent1 > areaMinExtent1 && cameraMaxExtent2 > areaMinExtent2 && areaMaxExtent0 > cameraMinExtent0 && areaMaxExtent1 > cameraMinExtent1 && areaMaxExtent2 > cameraMinExtent2) {
                if (!area.addedDynamicNodes) {
                    area.addedDynamicNodes = true;
                    combinedExtents[0] = (areaMinExtent0 < cameraMinExtent0 ? cameraMinExtent0 : areaMinExtent0);
                    combinedExtents[1] = (areaMinExtent1 < cameraMinExtent1 ? cameraMinExtent1 : areaMinExtent1);
                    combinedExtents[2] = (areaMinExtent2 < cameraMinExtent2 ? cameraMinExtent2 : areaMinExtent2);
                    combinedExtents[3] = (areaMaxExtent0 > cameraMaxExtent0 ? cameraMaxExtent0 : areaMaxExtent0);
                    combinedExtents[4] = (areaMaxExtent1 > cameraMaxExtent1 ? cameraMaxExtent1 : areaMaxExtent1);
                    combinedExtents[5] = (areaMaxExtent2 > cameraMaxExtent2 ? cameraMaxExtent2 : areaMaxExtent2);
                    dynamicSpatialMap.getOverlappingNodes(combinedExtents, nodes);
                }

                numNodes = nodes.length;
                for (n = 0; n < numNodes; n += 1) {
                    node = nodes[n];
                    if (node.queryCounter !== queryCounter) {
                        if (node.disabled) {
                            node.queryCounter = queryCounter;
                        } else if (isInsidePlanesAABB(node.worldExtents, portalPlanes)) {
                            sceneProcessVisibleNode(node, portalPlanes);
                        }
                    }
                }
            }
        }

        this.maxDistance = (maxDistance + camera.nearPlane);
        if (this.maxDistance < camera.farPlane) {
            this._filterVisibleNodesForCameraBox(camera, numVisibleNodes, numVisibleRenderables, numVisibleLights);
        } else {
            visibleRenderables.length = numVisibleRenderables;
            visibleLights.length = numVisibleLights;
            visibleNodes.length = numVisibleNodes;
        }

        return true;
    };

    //
    // _filterVisibleNodesForCameraBox
    //
    Scene.prototype._filterVisibleNodesForCameraBox = function (camera, numVisibleNodes, numVisibleRenderables, numVisibleLights) {
        var visibleNodes = this.visibleNodes;
        var visibleRenderables = this.visibleRenderables;
        var visibleLights = this.visibleLights;

        var oldNumVisibleRenderables = numVisibleRenderables;
        var oldNumVisibleLights = numVisibleLights;

        // The camera extents may be different and some objects could be discarded
        var cameraExtents = this.cameraExtents;

        camera.getFrustumExtents(cameraExtents, this.maxDistance);

        var cameraMinExtent0 = cameraExtents[0];
        var cameraMinExtent1 = cameraExtents[1];
        var cameraMinExtent2 = cameraExtents[2];
        var cameraMaxExtent0 = cameraExtents[3];
        var cameraMaxExtent1 = cameraExtents[4];
        var cameraMaxExtent2 = cameraExtents[5];

        var node, renderable, lightInstance, extents;
        var n = 0;
        while (n < numVisibleRenderables) {
            renderable = visibleRenderables[n];
            extents = renderable.getWorldExtents();
            if (extents[0] > cameraMaxExtent0 || extents[1] > cameraMaxExtent1 || extents[2] > cameraMaxExtent2 || extents[3] < cameraMinExtent0 || extents[4] < cameraMinExtent1 || extents[5] < cameraMinExtent2) {
                renderable.frameVisible -= 1;
                numVisibleRenderables -= 1;
                if (n < numVisibleRenderables) {
                    visibleRenderables[n] = visibleRenderables[numVisibleRenderables];
                } else {
                    break;
                }
            } else {
                n += 1;
            }
        }

        n = 0;
        while (n < numVisibleLights) {
            lightInstance = visibleLights[n];
            extents = lightInstance.getWorldExtents();
            if (extents[0] > cameraMaxExtent0 || extents[1] > cameraMaxExtent1 || extents[2] > cameraMaxExtent2 || extents[3] < cameraMinExtent0 || extents[4] < cameraMinExtent1 || extents[5] < cameraMinExtent2) {
                lightInstance.frameVisible -= 1;
                numVisibleLights -= 1;
                if (n < numVisibleLights) {
                    visibleLights[n] = visibleLights[numVisibleLights];
                } else {
                    break;
                }
            } else {
                n += 1;
            }
        }

        if (oldNumVisibleRenderables !== numVisibleRenderables || oldNumVisibleLights !== numVisibleLights) {
            n = 0;
            while (n < numVisibleNodes) {
                node = visibleNodes[n];
                extents = node.worldExtents;
                if (extents[0] > cameraMaxExtent0 || extents[1] > cameraMaxExtent1 || extents[2] > cameraMaxExtent2 || extents[3] < cameraMinExtent0 || extents[4] < cameraMinExtent1 || extents[5] < cameraMinExtent2) {
                    node.frameVisible -= 1;
                    numVisibleNodes -= 1;
                    if (n < numVisibleNodes) {
                        visibleNodes[n] = visibleNodes[numVisibleNodes];
                    } else {
                        break;
                    }
                } else {
                    n += 1;
                }
            }
        }

        visibleRenderables.length = numVisibleRenderables;
        visibleLights.length = numVisibleLights;
        visibleNodes.length = numVisibleNodes;
    };

    //
    // getCurrentVisibleNodes
    //
    Scene.prototype.getCurrentVisibleNodes = function () {
        return this.visibleNodes;
    };

    //
    // getCurrentVisibleRenderables
    //
    Scene.prototype.getCurrentVisibleRenderables = function () {
        return this.visibleRenderables;
    };

    //
    // getCurrentVisibleLights
    //
    Scene.prototype.getCurrentVisibleLights = function () {
        return this.visibleLights;
    };

    //
    // addRootNodeToUpdate
    //
    Scene.prototype.addRootNodeToUpdate = function (rootNode, name) {
        var dirtyRoots = this.dirtyRoots;
        if (dirtyRoots[name] !== rootNode) {
            dirtyRoots[name] = rootNode;
            var numNodesToUpdate = this.numNodesToUpdate;
            this.nodesToUpdate[numNodesToUpdate] = rootNode;
            this.numNodesToUpdate = (numNodesToUpdate + 1);
        }
    };

    //
    // updateNodes
    //
    Scene.prototype.updateNodes = function () {
        var numNodesToUpdate = this.numNodesToUpdate;
        if (0 < numNodesToUpdate) {
            var nodesToUpdate = this.nodesToUpdate;
            var dirtyRoots = this.dirtyRoots;
            var n;
            for (n = 0; n < numNodesToUpdate; n += 1) {
                dirtyRoots[nodesToUpdate[n].name] = null;
            }

            SceneNode.updateNodes(this.md, this, nodesToUpdate, numNodesToUpdate);

            this.numNodesToUpdate = 0;
        }
    };

    //
    // update
    //
    Scene.prototype.update = function () {
        this.updateNodes();
        this.staticSpatialMap.finalize();
        this.dynamicSpatialMap.finalize();
        this.updateExtents();

        if (this.areas && this.staticNodesChangeCounter !== this.areaInitalizeStaticNodesChangeCounter) {
            //Note this leaves extents of areas as large as they ever got.
            this.initializeAreas();
        }
    };

    //
    // updateExtents
    //
    Scene.prototype.updateExtents = function () {
        var rootStaticExtents = this.staticSpatialMap.getExtents();
        var rootDynamicExtents = this.dynamicSpatialMap.getExtents();
        var sceneExtents = this.extents;

        if (rootStaticExtents) {
            if (rootDynamicExtents) {
                var minStaticX, minStaticY, minStaticZ, maxStaticX, maxStaticY, maxStaticZ;
                var minDynamicX, minDynamicY, minDynamicZ, maxDynamicX, maxDynamicY, maxDynamicZ;

                minStaticX = rootStaticExtents[0];
                minStaticY = rootStaticExtents[1];
                minStaticZ = rootStaticExtents[2];
                maxStaticX = rootStaticExtents[3];
                maxStaticY = rootStaticExtents[4];
                maxStaticZ = rootStaticExtents[5];

                minDynamicX = rootDynamicExtents[0];
                minDynamicY = rootDynamicExtents[1];
                minDynamicZ = rootDynamicExtents[2];
                maxDynamicX = rootDynamicExtents[3];
                maxDynamicY = rootDynamicExtents[4];
                maxDynamicZ = rootDynamicExtents[5];

                sceneExtents[0] = (minStaticX < minDynamicX ? minStaticX : minDynamicX);
                sceneExtents[1] = (minStaticY < minDynamicY ? minStaticY : minDynamicY);
                sceneExtents[2] = (minStaticZ < minDynamicZ ? minStaticZ : minDynamicZ);
                sceneExtents[3] = (maxStaticX > maxDynamicX ? maxStaticX : maxDynamicX);
                sceneExtents[4] = (maxStaticY > maxDynamicY ? maxStaticY : maxDynamicY);
                sceneExtents[5] = (maxStaticZ > maxDynamicZ ? maxStaticZ : maxDynamicZ);
            } else {
                sceneExtents[0] = rootStaticExtents[0];
                sceneExtents[1] = rootStaticExtents[1];
                sceneExtents[2] = rootStaticExtents[2];
                sceneExtents[3] = rootStaticExtents[3];
                sceneExtents[4] = rootStaticExtents[4];
                sceneExtents[5] = rootStaticExtents[5];
            }
        } else {
            if (rootDynamicExtents) {
                sceneExtents[0] = rootDynamicExtents[0];
                sceneExtents[1] = rootDynamicExtents[1];
                sceneExtents[2] = rootDynamicExtents[2];
                sceneExtents[3] = rootDynamicExtents[3];
                sceneExtents[4] = rootDynamicExtents[4];
                sceneExtents[5] = rootDynamicExtents[5];
            } else {
                sceneExtents[0] = 0;
                sceneExtents[1] = 0;
                sceneExtents[2] = 0;
                sceneExtents[3] = 0;
                sceneExtents[4] = 0;
                sceneExtents[5] = 0;
            }
        }
    };

    //
    //  getExtents
    //
    Scene.prototype.getExtents = function () {
        if (0 < this.numNodesToUpdate) {
            this.updateNodes();
            this.staticSpatialMap.finalize();
            this.dynamicSpatialMap.finalize();
            this.updateExtents();
        }
        return this.extents;
    };

    //
    //  loadMaterial
    //
    Scene.prototype.loadMaterial = function (graphicsDevice, textureManager, effectManager, materialName, material) {
        var materials = this.materials;

        if (!materials[materialName]) {
            var effectName = material.effect || "default";
            var newMaterial = this.createMaterial(materialName, material, effectName, null, null, graphicsDevice);
            if (newMaterial) {
                delete newMaterial.effectName;
                var effect = effectManager.get(effectName);
                if (effect) {
                    effect.prepareMaterial(newMaterial);
                }
                newMaterial.loadTextures(textureManager);
                return true;
            }
        }
        return false;
    };

    //
    // hasMaterial
    //
    Scene.prototype.hasMaterial = function (materialName) {
        var material = this.materials[materialName];
        if (material) {
            return true;
        }
        return false;
    };

    //
    // getMaterial
    //
    Scene.prototype.getMaterial = function (materialName) {
        return this.materials[materialName];
    };

    //
    // Draw nodes with same technique, mostly for debugging
    //
    Scene.prototype.drawNodesArray = function (nodes, gd, globalMaterial, technique, renderUpdate) {
        var numNodes = nodes.length;
        if (numNodes > 0) {
            var setTechnique = gd.setTechnique;
            var setTechniqueParameters = gd.setTechniqueParameters;
            var setStream = gd.setStream;
            var setIndexBuffer = gd.setIndexBuffer;
            var drawIndexed = gd.drawIndexed;
            var draw = gd.draw;
            var currentSharedTechniqueParameters = null;
            var currentVertexBuffer = null;
            var currentSemantics = null;
            var currentOffset = -1;
            var node, shape, sharedTechniqueParameters, techniqueParameters, vertexBuffer, semantics, offset, surface, indexBuffer;
            var renderables, renderable, numRenderables, i;
            var n = 0;
            setTechnique.call(gd, technique);
            setTechniqueParameters.call(gd, globalMaterial);
            do {
                node = nodes[n];
                renderables = node.renderables;
                if (renderables) {
                    numRenderables = renderables.length;
                    for (i = 0; i < numRenderables; i += 1) {
                        renderable = renderables[i];

                        renderUpdate.call(renderable);

                        shape = renderable.geometry;
                        vertexBuffer = shape.vertexBuffer;
                        offset = shape.vertexOffset;
                        semantics = shape.semantics;
                        surface = renderable.surface;
                        sharedTechniqueParameters = renderable.sharedMaterial.techniqueParameters;
                        techniqueParameters = renderable.techniqueParameters;

                        if (currentSharedTechniqueParameters !== sharedTechniqueParameters) {
                            currentSharedTechniqueParameters = sharedTechniqueParameters;
                            setTechniqueParameters.call(gd, sharedTechniqueParameters, techniqueParameters);
                        } else {
                            setTechniqueParameters.call(gd, techniqueParameters);
                        }

                        if (currentVertexBuffer !== vertexBuffer || currentSemantics !== semantics || currentOffset !== offset) {
                            currentVertexBuffer = vertexBuffer;
                            currentSemantics = semantics;
                            currentOffset = offset;
                            setStream.call(gd, vertexBuffer, semantics, offset);
                        }

                        indexBuffer = surface.indexBuffer;
                        if (indexBuffer) {
                            setIndexBuffer.call(gd, indexBuffer);

                            drawIndexed.call(gd, surface.primitive, surface.numIndices, surface.first);
                        } else {
                            //Utilities.log("" + surface.primitive + " ," + surface.numVertices + " ," + surface.first);
                            draw.call(gd, surface.primitive, surface.numVertices, surface.first);
                        }
                    }
                }

                n += 1;
            } while(n < numNodes);
        }
    };

    Scene.prototype.drawVisibleNodes = function (gd, globalTechniqueParameters, technique, renderUpdate) {
        this.drawNodesArray(this.visibleNodes, gd, globalTechniqueParameters, technique, renderUpdate);
    };

    //
    // clearMaterials
    //
    Scene.prototype.clearMaterials = function () {
        var onMaterialDestroyed = this.onMaterialDestroyed;
        var materials = this.materials;
        if (materials) {
            for (var p in materials) {
                if (materials.hasOwnProperty(p)) {
                    materials[p].reference.unsubscribeDestroyed(onMaterialDestroyed);
                }
            }
        }
        this.materials = {};
    };

    //
    // clearShapes
    //
    Scene.prototype.clearShapes = function () {
        var onGeometryDestroyed = this.onGeometryDestroyed;
        var shapes = this.shapes;
        if (shapes) {
            for (var p in shapes) {
                if (shapes.hasOwnProperty(p)) {
                    shapes[p].reference.unsubscribeDestroyed(onGeometryDestroyed);
                }
            }
        }
        this.shapes = {};
    };

    //
    // clearShapesVertexData
    //
    Scene.prototype.clearShapesVertexData = function () {
        var shapes = this.shapes;
        var shape;
        if (shapes) {
            for (var p in shapes) {
                if (shapes.hasOwnProperty(p)) {
                    shape = shapes[p];
                    delete shape.vertexData;
                    delete shape.indexData;
                    var surfaces = shape.surfaces;
                    if (surfaces) {
                        for (var s in surfaces) {
                            if (surfaces.hasOwnProperty(s)) {
                                var surface = surfaces[s];
                                delete surface.vertexData;
                                delete surface.indexData;
                            }
                        }
                    }
                }
            }
        }
    };

    //
    // clearRootNodes
    //
    Scene.prototype.clearRootNodes = function () {
        var rootNodes = this.rootNodes;
        if (rootNodes) {
            var rootLength = rootNodes.length;
            for (var rootIndex = 0; rootIndex < rootLength; rootIndex += 1) {
                rootNodes[rootIndex].destroy();
            }
        }
        this.rootNodes = [];
        this.rootNodesMap = {};
        this.dirtyRoots = {};
        this.nodesToUpdate = [];
        this.numNodesToUpdate = 0;
    };

    //
    // clear
    //
    Scene.prototype.clear = function () {
        this.effects = [];
        this.effectsMap = {};
        this.semantics = {};
        this.lights = {};
        this.globalLights = [];
        this.clearRootNodes();
        this.clearMaterials();
        this.clearShapes();
        this.staticSpatialMap.clear();
        this.dynamicSpatialMap.clear();
        this.frustumPlanes = [];
        this.animations = {};
        this.skeletons = {};
        this.extents = this.md.aabbBuildEmpty();
        this.visibleNodes = [];
        this.visibleRenderables = [];
        this.visibleLights = [];
        this.cameraAreaIndex = -1;
        this.cameraExtents = this.md.aabbBuildEmpty();
        this.visiblePortals = [];
        this.frameIndex = 0;
        this.queryCounter = 0;
        this.staticNodesChangeCounter = 0;
        this.testExtents = this.md.aabbBuildEmpty();
        this.externalNodesStack = [];
        this.overlappingPortals = [];
        this.newPoints = [];
        this.queryVisibleNodes = [];
    };

    //
    // endLoading
    //
    Scene.prototype.endLoading = function (onload) {
        this.initializeNodes();
        this.initializeAreas();
        if (onload) {
            onload(this);
        }
    };

    //
    // initializeNodes
    //
    Scene.prototype.initializeNodes = function () {
        var numNodesToUpdate = this.numNodesToUpdate;
        if (0 < numNodesToUpdate) {
            this.numNodesToUpdate = 0;
            this.dirtyRoots = {};

            SceneNode.updateNodes(this.md, this, this.nodesToUpdate, numNodesToUpdate);
        }

        this.staticSpatialMap.finalize();

        this.updateExtents();
    };

    //
    // addAreaStaticNodes
    //
    Scene.prototype.addAreaStaticNodes = function () {
        var findAreaIndicesAABB = this.findAreaIndicesAABB;
        var findAreaIndex = this.findAreaIndex;
        var scene = this;

        var addAreasNode = function addAreasNodeFn(bspNodes, areas) {
            if (this.dynamic) {
                return;
            }

            if (this.hasRenderables() || (this.hasLightInstances() && this.worldExtents)) {
                var extents = this.worldExtents;
                var min0 = extents[0];
                var min1 = extents[1];
                var min2 = extents[2];
                var max0 = extents[3];
                var max1 = extents[4];
                var max2 = extents[5];
                var area, na;
                var cX, cY, cZ;
                if (!this.hasRenderables() && this.lightInstances.length === 1 && this.lightInstances[0].light.spot) {
                    var world = this.world;
                    cX = world[9];
                    cY = world[10];
                    cZ = world[11];
                } else {
                    cX = (min0 + max0) * 0.5;
                    cY = (min1 + max1) * 0.5;
                    cZ = (min2 + max2) * 0.5;
                }
                var areaIndex = findAreaIndex(bspNodes, cX, cY, cZ);
                if (areaIndex >= 0) {
                    area = areas[areaIndex];
                    area.nodes.push(this);

                    var overlappingAreas = scene.findOverlappingAreas(areaIndex, extents);
                    var numOverlappingAreas = overlappingAreas.length;
                    for (na = 0; na < numOverlappingAreas; na += 1) {
                        overlappingAreas[na].nodes.push(this);
                    }
                } else {
                    var areaFound = false;
                    var areaExtents;
                    for (; ;) {
                        var areaIndices = findAreaIndicesAABB(bspNodes, min0, min1, min2, max0, max1, max2);
                        var numAreaIndices = areaIndices.length;
                        if (0 < numAreaIndices) {
                            // 1st try: only attach to overlapping areas
                            na = 0;
                            do {
                                area = areas[areaIndices[na]];
                                areaExtents = area.extents;
                                if (areaExtents[0] <= max0 && areaExtents[1] <= max1 && areaExtents[2] <= max2 && areaExtents[3] >= min0 && areaExtents[4] >= min1 && areaExtents[5] >= min2) {
                                    area.nodes.push(this);
                                    areaFound = true;
                                }
                                na += 1;
                            } while(na < numAreaIndices);
                            if (!areaFound) {
                                // 2nd try: attach to any areas from bsp query
                                na = 0;
                                do {
                                    areas[areaIndices[na]].nodes.push(this);
                                    na += 1;
                                } while(na < numAreaIndices);
                            }
                            break;
                        } else {
                            // 3nd try: increase bounding box
                            var delta = Math.max((max0 - min0), (max1 - min1), (max2 - min2)) / 20;
                            min0 -= delta;
                            min1 -= delta;
                            min2 -= delta;
                            max0 += delta;
                            max1 += delta;
                            max2 += delta;
                        }
                    }
                }
            }
            var children = this.children;
            if (children) {
                var numChildren = children.length;
                for (var nc = 0; nc < numChildren; nc += 1) {
                    addAreasNode.call(children[nc], bspNodes, areas);
                }
            }
        };

        var rootNodes = this.rootNodes;
        var numRootNodes = rootNodes.length;
        var bspNodes = this.bspNodes;
        var areas = this.areas;
        for (var n = 0; n < numRootNodes; n += 1) {
            addAreasNode.call(rootNodes[n], bspNodes, areas);
        }
    };

    //
    // findOverlappingAreas
    //
    Scene.prototype.findOverlappingAreas = function (startAreaIndex, extents, avoidDisabled) {
        var area, portals, numPortals, n, portal, plane, d0, d1, d2, portalExtents, areaIndex, nextArea;
        var queryCounter = this.getQueryCounter();
        var areas = this.areas;
        var portalsStack = [];
        var numPortalsStack = 0;
        var overlappingAreas = [];
        var numOverlappingAreas = 0;

        var min0 = extents[0];
        var min1 = extents[1];
        var min2 = extents[2];
        var max0 = extents[3];
        var max1 = extents[4];
        var max2 = extents[5];

        area = areas[startAreaIndex];
        area.queryCounter = queryCounter;

        portals = area.portals;
        numPortals = portals.length;
        for (n = 0; n < numPortals; n += 1) {
            portal = portals[n];
            if (avoidDisabled && portal.disabled) {
                continue;
            }
            portal.queryCounter = queryCounter;

            portalExtents = portal.extents;
            if (portalExtents[0] < max0 && portalExtents[1] < max1 && portalExtents[2] < max2 && portalExtents[3] > min0 && portalExtents[4] > min1 && portalExtents[5] > min2) {
                plane = portal.plane;
                d0 = plane[0];
                d1 = plane[1];
                d2 = plane[2];
                if ((d0 * (d0 < 0 ? min0 : max0) + d1 * (d1 < 0 ? min1 : max1) + d2 * (d2 < 0 ? min2 : max2)) >= plane[3]) {
                    portalsStack[numPortalsStack] = portal;
                    numPortalsStack += 1;
                }
            }
        }

        while (0 < numPortalsStack) {
            numPortalsStack -= 1;
            portal = portalsStack[numPortalsStack];

            areaIndex = portal.area;
            area = areas[areaIndex];
            if (area.queryCounter !== queryCounter) {
                area.queryCounter = queryCounter;
                overlappingAreas[numOverlappingAreas] = area;
                numOverlappingAreas += 1;
            }

            portals = area.portals;
            numPortals = portals.length;
            for (n = 0; n < numPortals; n += 1) {
                portal = portals[n];
                if (avoidDisabled && portal.disabled) {
                    continue;
                }
                nextArea = portal.area;
                if (nextArea !== areaIndex && nextArea !== startAreaIndex && portal.queryCounter !== queryCounter) {
                    portal.queryCounter = queryCounter;

                    portalExtents = portal.extents;
                    if (portalExtents[0] < max0 && portalExtents[1] < max1 && portalExtents[2] < max2 && portalExtents[3] > min0 && portalExtents[4] > min1 && portalExtents[5] > min2) {
                        plane = portal.plane;
                        d0 = plane[0];
                        d1 = plane[1];
                        d2 = plane[2];
                        if ((d0 * (d0 < 0 ? min0 : max0) + d1 * (d1 < 0 ? min1 : max1) + d2 * (d2 < 0 ? min2 : max2)) >= plane[3]) {
                            portalsStack[numPortalsStack] = portal;
                            numPortalsStack += 1;
                        }
                    }
                }
            }
        }

        return overlappingAreas;
    };

    //
    // checkAreaDynamicNodes
    //
    Scene.prototype.checkAreaDynamicNodes = function () {
        var findAreaIndicesAABB = this.findAreaIndicesAABB;
        var dynamicSpatialMap = this.dynamicSpatialMap;
        var bspNodes = this.bspNodes;
        var areas = this.areas;

        var checkAreaNode = function checkAreaNodeFn() {
            if (this.dynamic && (this.hasRenderables() || (this.hasLightInstances() && this.worldExtents))) {
                var extents = this.worldExtents;
                var min0 = extents[0];
                var min1 = extents[1];
                var min2 = extents[2];
                var max0 = extents[3];
                var max1 = extents[4];
                var max2 = extents[5];
                var pad = false;
                var areaFound = false;
                var na;
                for (; ;) {
                    var areaIndices = findAreaIndicesAABB(bspNodes, min0, min1, min2, max0, max1, max2);
                    var numAreaIndices = areaIndices.length;
                    if (0 < numAreaIndices) {
                        na = 0;
                        do {
                            var area = areas[areaIndices[na]];
                            var areaExtent = area.extents;
                            if (areaExtent[0] <= max0 && areaExtent[1] <= max1 && areaExtent[2] <= max2 && areaExtent[3] >= min0 && areaExtent[4] >= min1 && areaExtent[5] >= min2) {
                                areaFound = true;
                                break;
                            }
                            na += 1;
                        } while(na < numAreaIndices);
                    }
                    if (areaFound) {
                        break;
                    }
                    var delta = Math.max((max0 - min0), (max1 - min1), (max2 - min2)) / 20;
                    min0 -= delta;
                    min1 -= delta;
                    min2 -= delta;
                    max0 += delta;
                    max1 += delta;
                    max2 += delta;
                    pad = true;
                }
                if (pad) {
                    extents[0] = min0;
                    extents[1] = min1;
                    extents[2] = min2;
                    extents[3] = max0;
                    extents[4] = max1;
                    extents[5] = max2;
                    dynamicSpatialMap.update(this, extents);
                }
            }
            var children = this.children;
            if (children) {
                var numChildren = children.length;
                for (var nc = 0; nc < numChildren; nc += 1) {
                    checkAreaNode.call(children[nc]);
                }
            }
        };

        var rootNodes = this.rootNodes;
        var numRootNodes = rootNodes.length;
        for (var n = 0; n < numRootNodes; n += 1) {
            checkAreaNode.call(rootNodes[n]);
        }
    };

    //
    // initializeAreas
    //
    Scene.prototype.initializeAreas = function () {
        var areas = this.areas;
        if (areas) {
            var numAreas = areas.length;
            var n, area, target, extents, areaExtents;
            for (n = 0; n < numAreas; n += 1) {
                area = areas[n];
                target = area.target;
                area.nodes = [];
                extents = target.calculateHierarchyWorldExtents();
                if (extents) {
                    areaExtents = area.extents;
                    areaExtents[0] = (extents[0] < areaExtents[0] ? extents[0] : areaExtents[0]);
                    areaExtents[1] = (extents[1] < areaExtents[1] ? extents[1] : areaExtents[1]);
                    areaExtents[2] = (extents[2] < areaExtents[2] ? extents[2] : areaExtents[2]);
                    areaExtents[3] = (extents[3] > areaExtents[3] ? extents[3] : areaExtents[3]);
                    areaExtents[4] = (extents[4] > areaExtents[4] ? extents[4] : areaExtents[4]);
                    areaExtents[5] = (extents[5] > areaExtents[5] ? extents[5] : areaExtents[5]);
                }
            }

            this.addAreaStaticNodes();

            this.checkAreaDynamicNodes();

            for (n = 0; n < numAreas; n += 1) {
                area = areas[n];
                area.numStaticNodes = area.nodes.length;
            }
        }
        this.areaInitalizeStaticNodesChangeCounter = this.staticNodesChangeCounter;
    };

    //
    // createMaterial
    //
    Scene.prototype.createMaterial = function (materialName, fileMaterial, effectName, fileEffects, fileImages, graphicsDevice) {
        var materials = this.materials;

        var material = Material.create(graphicsDevice);
        var param, filename, effectType, p;
        var fileEffectMeta;

        if (fileEffects) {
            var fileEffect = fileEffects[effectName];
            if (fileEffect) {
                var effectParameters = fileEffect.parameters;
                for (p in effectParameters) {
                    if (effectParameters.hasOwnProperty(p)) {
                        param = effectParameters[p];
                        if (typeof param === 'string') {
                            if (fileImages) {
                                filename = fileImages[param] || param;
                            } else {
                                filename = param;
                            }

                            if (!material.texturesNames) {
                                material.texturesNames = {};
                            }
                            material.texturesNames[p] = filename;
                            material.techniqueParameters[p] = null;
                        } else {
                            material.techniqueParameters[p] = param;
                        }
                    }
                }
                effectType = fileEffect.type;
                fileEffectMeta = fileEffect.meta;
            } else {
                effectType = effectName;
            }
        } else {
            effectType = effectName;
        }

        var materialParameters = fileMaterial.parameters;
        for (p in materialParameters) {
            if (materialParameters.hasOwnProperty(p)) {
                param = materialParameters[p];
                if (typeof param === 'string') {
                    if (fileImages) {
                        filename = fileImages[param] || param;
                    } else {
                        filename = param;
                    }

                    if (!material.texturesNames) {
                        material.texturesNames = {};
                    }
                    material.texturesNames[p] = filename;

                    material.techniqueParameters[p] = null;
                } else {
                    material.techniqueParameters[p] = param;
                }
            }
        }

        material.effectName = effectType;

        var fileMaterialMeta = fileMaterial.meta;
        if (fileMaterialMeta) {
            if (fileEffectMeta) {
                for (p in fileEffectMeta) {
                    if (fileEffectMeta.hasOwnProperty(p) && !fileMaterialMeta.hasOwnProperty(p)) {
                        fileMaterialMeta[p] = fileEffectMeta[p];
                    }
                }
            }
            material.meta = fileMaterialMeta;
        } else if (fileEffectMeta) {
            material.meta = fileEffectMeta;
        }

        materials[materialName] = material;
        material.name = materialName;
        material.reference.subscribeDestroyed(this.onMaterialDestroyed);

        return material;
    };

    //
    // loadMaterials
    //
    Scene.prototype.loadMaterials = function (loadParams) {
        var sceneData = loadParams.data;
        var gd = loadParams.graphicsDevice;
        var textureManager = loadParams.textureManager;
        var createMaterial = this.createMaterial;

        if (!loadParams.append) {
            this.effects = [];
            this.effectsMap = {};
            this.clearMaterials();
        }

        // Import materials
        var fileMaterials = sceneData.materials;
        if (fileMaterials) {
            var fileImages = sceneData.images;
            var fileEffects = sceneData.effects;
            var materials = this.materials;
            for (var m in fileMaterials) {
                if (fileMaterials.hasOwnProperty(m) && !materials[m]) {
                    var fileMaterial = fileMaterials[m];
                    var effectName = (fileMaterial.effect || "default");
                    createMaterial.call(this, m, fileMaterial, effectName, fileEffects, fileImages, gd, textureManager);
                }
            }
        }
    };

    //
    // loadSkeletons
    //
    Scene.prototype.loadSkeletons = function (loadParams) {
        var sceneData = loadParams.data;
        var fileSkeletons = sceneData.skeletons;

        var md = this.md;
        var m43Build = md.m43Build;

        var invLTM, bindPose;

        for (var s in fileSkeletons) {
            if (fileSkeletons.hasOwnProperty(s)) {
                var skeleton = fileSkeletons[s];

                var numJoints = skeleton.numNodes;
                var invLTMs = skeleton.invBoneLTMs;
                var bindPoses = skeleton.bindPoses;

                for (var b = 0; b < numJoints; b += 1) {
                    invLTM = invLTMs[b];
                    bindPose = bindPoses[b];

                    invLTMs[b] = m43Build.apply(md, invLTM);
                    bindPoses[b] = m43Build.apply(md, bindPose);
                }

                if (loadParams.skeletonNamePrefix) {
                    s = loadParams.skeletonNamePrefix + s;
                }

                this.skeletons[s] = skeleton;
            }
        }
    };

    // For cases where > 1-index per vertex we process it to create 1-index per vertex from data
    Scene.prototype._updateSingleIndexTables = function (surface, indicesPerVertex, verticesAsIndexLists, verticesAsIndexListTable, numUniqueVertices) {
        var faces = surface.faces;
        var numIndices = faces.length;

        var newFaces = [];
        newFaces.length = numIndices;

        var numUniqueVertIndex = verticesAsIndexLists.length;
        var vertIdx = 0;
        var srcIdx = 0;
        var n, maxn, index;
        var currentLevel, nextLevel, thisVertIndex;

        while (srcIdx < numIndices) {
            currentLevel = verticesAsIndexListTable;
            n = srcIdx;
            maxn = (srcIdx + (indicesPerVertex - 1));
            do {
                index = faces[n];
                nextLevel = currentLevel[index];
                if (nextLevel === undefined) {
                    currentLevel[index] = nextLevel = {};
                }
                currentLevel = nextLevel;
                n += 1;
            } while(n < maxn);

            index = faces[n];
            thisVertIndex = currentLevel[index];
            if (thisVertIndex === undefined) {
                // New index - add to tables
                currentLevel[index] = thisVertIndex = numUniqueVertices;
                numUniqueVertices += 1;

                // Copy indices
                n = srcIdx;
                do {
                    verticesAsIndexLists[numUniqueVertIndex] = faces[n];
                    numUniqueVertIndex += 1;
                    n += 1;
                } while(n < maxn);

                verticesAsIndexLists[numUniqueVertIndex] = index;
                numUniqueVertIndex += 1;
            }

            newFaces[vertIdx] = thisVertIndex;
            vertIdx += 1;

            srcIdx += indicesPerVertex;
        }

        newFaces.length = vertIdx;
        surface.faces = newFaces;

        return numUniqueVertices;
    };

    Scene.prototype._isSequentialIndices = function (indices, numIndices) {
        var baseIndex = indices[0];
        var n;
        for (n = 1; n < numIndices; n += 1) {
            if (indices[n] !== (baseIndex + n)) {
                return false;
            }
        }
        return true;
    };

    // try to group sequential renderables into a single draw call
    Scene.prototype._optimizeRenderables = function (node, gd) {
        var renderables = node.renderables;
        var numRenderables = renderables.length;
        var triangles = gd.PRIMITIVE_TRIANGLES;
        var vbMap = {};
        var ungroup = [];
        var numUngroup = 0;
        var n, renderable, geometry, surface, vbid, ibMap, ibid, group;
        var foundGroup = false;
        for (n = 0; n < numRenderables; n += 1) {
            renderable = renderables[n];
            surface = renderable.surface;

            if (surface.primitive === triangles && renderable.geometryType === "rigid") {
                geometry = renderable.geometry;
                vbid = geometry.vertexBuffer.id;
                ibMap = vbMap[vbid];
                if (ibMap === undefined) {
                    vbMap[vbid] = ibMap = {};
                }
                if (surface.indexBuffer) {
                    ibid = surface.indexBuffer.id;
                } else {
                    ibid = 'null';
                }
                group = ibMap[ibid];
                if (group === undefined) {
                    ibMap[ibid] = [renderable];
                } else {
                    group.push(renderable);
                    foundGroup = true;
                }
            } else {
                ungroup[numUngroup] = renderable;
                numUngroup += 1;
            }
        }

        function cloneSurface(surface) {
            var clone = new surface.constructor();
            var p;
            for (p in surface) {
                if (surface.hasOwnProperty(p)) {
                    clone[p] = surface[p];
                }
            }
            return clone;
        }

        if (foundGroup) {
            var max = Math.max;
            var min = Math.min;
            var arrayConstructor = (this.float32ArrayConstructor ? this.float32ArrayConstructor : Array);
            var sequenceExtents = new arrayConstructor(6);
            var sequenceFirstRenderable, sequenceLength, sequenceVertexOffset, sequenceIndicesEnd, sequenceNumVertices;
            var groupSize, g, lastMaterial, material, center, halfExtents;

            var flushSequence = function flushSequenceFn() {
                var surface = cloneSurface(sequenceFirstRenderable.surface);
                sequenceFirstRenderable.surface = surface;
                if (surface.indexBuffer) {
                    surface.numIndices = (sequenceIndicesEnd - surface.first);
                    surface.numVertices = sequenceNumVertices;
                } else {
                    surface.numVertices = (sequenceIndicesEnd - surface.first);
                }

                var c0 = (sequenceExtents[3] + sequenceExtents[0]) * 0.5;
                var c1 = (sequenceExtents[4] + sequenceExtents[1]) * 0.5;
                var c2 = (sequenceExtents[5] + sequenceExtents[2]) * 0.5;
                if (c0 !== 0 || c1 !== 0 || c2 !== 0) {
                    var center = (sequenceFirstRenderable.center || new arrayConstructor(3));
                    sequenceFirstRenderable.center = center;
                    center[0] = c0;
                    center[1] = c1;
                    center[2] = c2;
                } else {
                    sequenceFirstRenderable.center = null;
                }

                var halfExtents = (sequenceFirstRenderable.halfExtents || new arrayConstructor(3));
                sequenceFirstRenderable.halfExtents = halfExtents;
                halfExtents[0] = (sequenceExtents[3] - sequenceExtents[0]) * 0.5;
                halfExtents[1] = (sequenceExtents[4] - sequenceExtents[1]) * 0.5;
                halfExtents[2] = (sequenceExtents[5] - sequenceExtents[2]) * 0.5;
            };

            numRenderables = 0;
            for (vbid in vbMap) {
                if (vbMap.hasOwnProperty(vbid)) {
                    ibMap = vbMap[vbid];
                    for (ibid in ibMap) {
                        if (ibMap.hasOwnProperty(ibid)) {
                            group = ibMap[ibid];
                            groupSize = group.length;
                            if (groupSize === 1) {
                                renderables[numRenderables] = group[0];
                                numRenderables += 1;
                            } else {
                                group.sort(function (a, b) {
                                    return (a.geometry.vertexOffset - b.geometry.vertexOffset) || (a.surface.first - b.surface.first);
                                });

                                g = 0;
                                lastMaterial = null;
                                sequenceFirstRenderable = null;
                                sequenceNumVertices = 0;
                                sequenceVertexOffset = -1;
                                sequenceIndicesEnd = 0;
                                sequenceLength = 0;
                                do {
                                    renderable = group[g];
                                    geometry = renderable.geometry;
                                    surface = renderable.surface;
                                    material = renderable.sharedMaterial;
                                    center = renderable.center;
                                    halfExtents = renderable.halfExtents;
                                    if (sequenceVertexOffset !== geometry.vertexOffset || sequenceIndicesEnd !== surface.first || !lastMaterial || (lastMaterial !== material && !lastMaterial.isSimilar(material))) {
                                        if (0 < sequenceLength) {
                                            if (1 < sequenceLength) {
                                                flushSequence();
                                            }

                                            renderables[numRenderables] = sequenceFirstRenderable;
                                            numRenderables += 1;
                                        }

                                        lastMaterial = material;
                                        sequenceFirstRenderable = renderable;
                                        sequenceNumVertices = 0;
                                        sequenceLength = 1;
                                        sequenceVertexOffset = geometry.vertexOffset;

                                        if (center) {
                                            sequenceExtents[0] = (center[0] - halfExtents[0]);
                                            sequenceExtents[1] = (center[1] - halfExtents[1]);
                                            sequenceExtents[2] = (center[2] - halfExtents[2]);
                                            sequenceExtents[3] = (center[0] + halfExtents[0]);
                                            sequenceExtents[4] = (center[1] + halfExtents[1]);
                                            sequenceExtents[5] = (center[2] + halfExtents[2]);
                                        } else {
                                            sequenceExtents[0] = -halfExtents[0];
                                            sequenceExtents[1] = -halfExtents[1];
                                            sequenceExtents[2] = -halfExtents[2];
                                            sequenceExtents[3] = halfExtents[0];
                                            sequenceExtents[4] = halfExtents[1];
                                            sequenceExtents[5] = halfExtents[2];
                                        }
                                    } else {
                                        sequenceLength += 1;

                                        if (center) {
                                            sequenceExtents[0] = min(sequenceExtents[0], (center[0] - halfExtents[0]));
                                            sequenceExtents[1] = min(sequenceExtents[1], (center[1] - halfExtents[1]));
                                            sequenceExtents[2] = min(sequenceExtents[2], (center[2] - halfExtents[2]));
                                            sequenceExtents[3] = max(sequenceExtents[3], (center[0] + halfExtents[0]));
                                            sequenceExtents[4] = max(sequenceExtents[4], (center[1] + halfExtents[1]));
                                            sequenceExtents[5] = max(sequenceExtents[5], (center[2] + halfExtents[2]));
                                        } else {
                                            sequenceExtents[0] = min(sequenceExtents[0], -halfExtents[0]);
                                            sequenceExtents[1] = min(sequenceExtents[1], -halfExtents[1]);
                                            sequenceExtents[2] = min(sequenceExtents[2], -halfExtents[2]);
                                            sequenceExtents[3] = max(sequenceExtents[3], halfExtents[0]);
                                            sequenceExtents[4] = max(sequenceExtents[4], halfExtents[1]);
                                            sequenceExtents[5] = max(sequenceExtents[5], halfExtents[2]);
                                        }
                                    }

                                    if (surface.indexBuffer) {
                                        sequenceIndicesEnd = (surface.first + surface.numIndices);
                                        sequenceNumVertices += surface.numVertices;
                                    } else {
                                        sequenceIndicesEnd = (surface.first + surface.numVertices);
                                    }

                                    g += 1;
                                } while(g < groupSize);

                                /* debug.assert(0 < sequenceLength); */

                                if (1 < sequenceLength) {
                                    flushSequence();
                                }

                                renderables[numRenderables] = sequenceFirstRenderable;
                                numRenderables += 1;
                            }
                        }
                    }
                }
            }
            for (n = 0; n < numUngroup; n += 1) {
                renderables[numRenderables] = ungroup[n];
                numRenderables += 1;
            }
            for (n = numRenderables; n < renderables.length; n += 1) {
                renderables[n].setNode(null);
            }
            renderables.length = numRenderables;
        }
    };

    //
    // loadShape
    //
    Scene.prototype.loadShape = function (shapeName, fileShapeName, loadParams) {
        var shape = this.shapes[shapeName];

        if (!shape) {
            var cachedSemantics = this.semantics;

            var sceneData = loadParams.data;
            var gd = loadParams.graphicsDevice;
            var keepVertexData = loadParams.keepVertexData;
            var fileShapes = sceneData.geometries;
            var fileShape = fileShapes[fileShapeName];
            var sources = fileShape.sources;
            var inputs = fileShape.inputs;
            var skeletonName = loadParams.skeletonNamePrefix ? loadParams.skeletonNamePrefix + fileShape.skeleton : fileShape.skeleton;

            shape = Geometry.create();

            if (skeletonName) {
                var skeleton = this.skeletons[skeletonName];
                if (skeleton) {
                    shape.skeleton = skeleton;
                    shape.type = "skinned";
                } else {
                    // Failed to load skeleton so just draw bind pose
                    shape.type = "rigid";
                }
            } else {
                shape.type = "rigid";
            }

            if (gd) {
                // First calculate data about the vertex streams
                var offset, stride;
                var destStride;
                var destFormat;
                var maxOffset = 0;
                var vertexSources = [];

                var isUByte4Range = function isUByte4RangeFn(minVal, maxVal) {
                    return (minVal >= 0) && (maxVal <= 255) && (maxVal >= 0);
                };

                var areInRange = function areInRangeFn(minVals, maxVals, isRangeFn) {
                    var numVals = minVals.length;
                    if (maxVals.length !== numVals) {
                        return false;
                    }
                    for (var valIdx = 0; valIdx < numVals; valIdx += 1) {
                        if (!isRangeFn(minVals[valIdx], maxVals[valIdx])) {
                            return false;
                        }
                    }
                    return true;
                };

                var formatMap = loadParams.vertexFormatMap || {};

                var fileInput;
                for (var input in inputs) {
                    if (inputs.hasOwnProperty(input)) {
                        if (gd['SEMANTIC_' + input] === undefined) {
                            /* debug.log("Unknown semantic: " + input); */
                            continue;
                        }

                        fileInput = inputs[input];
                        offset = fileInput.offset;
                        if (offset > maxOffset) {
                            maxOffset = offset;
                        }
                        var fileSource = sources[fileInput.source];
                        var fileSourceStride = fileSource.stride;

                        // If the caller gave a preferred format, try
                        // to use it.
                        destFormat = formatMap[input];
                        destStride = fileSourceStride;

                        if (destFormat) {
                            if (destFormat.indexOf("4")) {
                                destStride = 4;
                            } else if (destFormat.indexOf("3")) {
                                destStride = 3;
                            } else if (destFormat.indexOf("2")) {
                                destStride = 2;
                            } else if (destFormat.indexOf("1")) {
                                destStride = 1;
                            } else {
                                destFormat = null;
                            }
                        }

                        if (!destFormat) {
                            if (input === "BLENDINDICES" || input === "BLENDINDICES0") {
                                if (fileSourceStride === 4 && areInRange(fileSource.min, fileSource.max, isUByte4Range)) {
                                    destFormat = "UBYTE4";
                                }
                            }
                            // if (input == "NORMAL" || input == "NORMAL0")
                            // {
                            //     if (fileSourceStride == 3)
                            //     {
                            //         Check range is within [-1,1]
                            //         destFormat = "BYTE";
                            //         destFormatNormalized = true;
                            //         destStride = 4;
                            //     }
                            // }
                        }

                        if (!destFormat) {
                            destFormat = "FLOAT" + fileSourceStride;
                        }

                        vertexSources.push({
                            semantic: input,
                            offset: offset,
                            data: fileSource.data,
                            stride: fileSourceStride,
                            destFormat: destFormat,
                            destStride: destStride
                        });
                    }
                }
                var indicesPerVertex = (maxOffset + 1);

                if (0 < maxOffset) {
                    var vertexSourcesCompare = function (vertexSourceA, vertexSourceB) {
                        if (vertexSourceA.offset === vertexSourceB.offset) {
                            var semanticA = vertexSourceA.semantic;
                            if (typeof semanticA === 'string') {
                                semanticA = gd['SEMANTIC_' + semanticA];
                            }
                            var semanticB = vertexSourceB.semantic;
                            if (typeof semanticB === 'string') {
                                semanticB = gd['SEMANTIC_' + semanticB];
                            }
                            return (semanticA - semanticB);
                        } else {
                            return (vertexSourceA.offset - vertexSourceB.offset);
                        }
                    };
                    vertexSources.sort(vertexSourcesCompare);
                }

                var numVertexSources = vertexSources.length;
                var semanticsNames = [];
                var attributes = [];
                var useFloatArray = (this.float32ArrayConstructor ? true : false);
                var numValuesPerVertex = 0;
                var vs, vertexSource;
                for (vs = 0; vs < numVertexSources; vs += 1) {
                    vertexSource = vertexSources[vs];
                    semanticsNames[vs] = vertexSource.semantic;
                    destFormat = vertexSource.destFormat;
                    if (useFloatArray) {
                        if (typeof destFormat === "string") {
                            if (destFormat[0] !== "F") {
                                useFloatArray = false;
                            }
                        } else {
                            if (destFormat !== gd.VERTEXFORMAT_FLOAT1 && destFormat !== gd.VERTEXFORMAT_FLOAT2 && destFormat !== gd.VERTEXFORMAT_FLOAT3 && destFormat !== gd.VERTEXFORMAT_FLOAT4) {
                                useFloatArray = false;
                            }
                        }
                    }
                    attributes[vs] = destFormat;
                    numValuesPerVertex += vertexSource.stride;
                }

                // Now parse the surfaces to work out primitive types and the total vertex count
                var numVertices, totalNumVertices = 0;
                var noSurfaces = false;
                var surfaces = fileShape.surfaces;
                if (!surfaces) {
                    noSurfaces = true;
                    surfaces = {
                        singleSurface: {
                            triangles: fileShape.triangles,
                            lines: fileShape.lines,
                            numPrimitives: fileShape.numPrimitives
                        }
                    };
                }

                var surface;
                var destSurface;
                var faces;
                var s;

                for (s in surfaces) {
                    if (surfaces.hasOwnProperty(s)) {
                        surface = surfaces[s];
                        destSurface = {};
                        shape.surfaces[s] = destSurface;

                        faces = surface.triangles;
                        var primitive, vertexPerPrimitive;
                        if (faces) {
                            primitive = gd.PRIMITIVE_TRIANGLES;
                            vertexPerPrimitive = 3;
                        } else {
                            faces = surface.lines;
                            if (faces) {
                                primitive = gd.PRIMITIVE_LINES;
                                vertexPerPrimitive = 2;
                            }
                        }
                        destSurface.primitive = primitive;
                        destSurface.faces = faces;

                        if (faces) {
                            if (1 < indicesPerVertex) {
                                numVertices = (surface.numPrimitives * vertexPerPrimitive);
                                destSurface.numVertices = numVertices;
                            } else {
                                numVertices = (vertexSources[0].data.length / vertexSources[0].stride);
                                if (numVertices > faces.length) {
                                    numVertices = faces.length;
                                }
                                destSurface.numVertices = numVertices;
                            }
                        }
                    }
                }

                if (indicesPerVertex > 1) {
                    // [ [a,b,c], [d,e,f], ... ]
                    totalNumVertices = 0;

                    var verticesAsIndexLists = [];
                    var verticesAsIndexListTable = {};
                    var shapeSurfaces = shape.surfaces;
                    for (s in shapeSurfaces) {
                        if (shapeSurfaces.hasOwnProperty(s)) {
                            var shapeSurface = shapeSurfaces[s];
                            totalNumVertices = this._updateSingleIndexTables(shapeSurface, indicesPerVertex, verticesAsIndexLists, verticesAsIndexListTable, totalNumVertices);
                        }
                    }

                    verticesAsIndexListTable = null;

                    for (vs = 0; vs < numVertexSources; vs += 1) {
                        vertexSource = vertexSources[vs];
                        var thisSourceOffset = vertexSource.offset;
                        var thisSourceStride = vertexSource.stride;
                        var thisSourceData = vertexSource.data;

                        var newData = new Array(thisSourceStride * totalNumVertices);

                        // For each entry in index list
                        var vertIdx = 0;
                        var vertIdxOffset = thisSourceOffset;
                        while (vertIdx < totalNumVertices) {
                            var newVBIdx = thisSourceStride * vertIdx;
                            var oldVBIdx = thisSourceStride * verticesAsIndexLists[vertIdxOffset];

                            for (var attrIdx = 0; attrIdx < thisSourceStride; attrIdx += 1) {
                                newData[newVBIdx + attrIdx] = thisSourceData[oldVBIdx + attrIdx];
                            }

                            vertIdx += 1;
                            vertIdxOffset += indicesPerVertex;
                        }

                        vertexSource.data = newData;
                        vertexSource.offset = 0;
                    }

                    verticesAsIndexLists.length = 0;
                    verticesAsIndexLists = null;

                    indicesPerVertex = 1;
                }

                /* debug.assert(indicesPerVertex === 1); */

                totalNumVertices = vertexSources[0].data.length / vertexSources[0].stride;

                var vertexBufferManager = (loadParams.vertexBufferManager || this.vertexBufferManager);
                if (!vertexBufferManager) {
                    vertexBufferManager = VertexBufferManager.create(gd);
                    this.vertexBufferManager = vertexBufferManager;
                }

                var indexBufferManager = (loadParams.indexBufferManager || this.indexBufferManager);
                if (!indexBufferManager) {
                    indexBufferManager = IndexBufferManager.create(gd);
                    this.indexBufferManager = indexBufferManager;
                }

                var baseIndex;
                var vertexBuffer = null;
                var vertexBufferAllocation = vertexBufferManager.allocate(totalNumVertices, attributes);
                vertexBuffer = vertexBufferAllocation.vertexBuffer;
                if (!vertexBuffer) {
                    return undefined;
                }

                shape.vertexBuffer = vertexBuffer;
                shape.vertexBufferManager = vertexBufferManager;
                shape.vertexBufferAllocation = vertexBufferAllocation;

                baseIndex = vertexBufferAllocation.baseIndex;

                var indexBufferAllocation;
                var t, index, nextIndex;

                //
                // We no have the simple case of each index maps to one vertex so create one vertex buffer and fill in.
                //
                var vertexData = (useFloatArray ? new this.float32ArrayConstructor(totalNumVertices * numValuesPerVertex) : new Array(totalNumVertices * numValuesPerVertex));
                var vertexDataCount = 0;
                for (t = 0; t < totalNumVertices; t += 1) {
                    vs = 0;
                    do {
                        vertexSource = vertexSources[vs];
                        var sourceData = vertexSource.data;
                        stride = vertexSource.stride;
                        index = t * stride;
                        nextIndex = (index + stride);
                        destStride = vertexSource.destStride;
                        do {
                            vertexData[vertexDataCount] = sourceData[index];
                            vertexDataCount += 1;
                            index += 1;
                        } while(index < nextIndex);

                        while (stride < destStride) {
                            vertexData[vertexDataCount] = 0;
                            vertexDataCount += 1;
                            destStride -= 1;
                        }

                        vs += 1;
                    } while(vs < numVertexSources);
                }
                vertexBuffer.setData(vertexData, baseIndex, totalNumVertices);

                if (keepVertexData && !useFloatArray && this.float32ArrayConstructor) {
                    vertexData = new this.float32ArrayConstructor(vertexData);
                }

                // Count total num indices
                var totalNumIndices = 0;
                var numIndices;

                for (s in surfaces) {
                    if (surfaces.hasOwnProperty(s)) {
                        destSurface = shape.surfaces[s];
                        faces = destSurface.faces;
                        if (faces) {
                            numIndices = faces.length;

                            if (numIndices === 6 && totalNumVertices === 4 && destSurface.primitive === gd.PRIMITIVE_TRIANGLES) {
                                var f0 = faces[0];
                                if (f0 === faces[3] || f0 === faces[4] || f0 === faces[5]) {
                                    faces[0] = faces[1];
                                    faces[1] = faces[2];
                                    faces[2] = f0;

                                    f0 = faces[0];
                                    if (f0 === faces[3] || f0 === faces[4] || f0 === faces[5]) {
                                        faces[0] = faces[1];
                                        faces[1] = faces[2];
                                        faces[2] = f0;
                                    }
                                }
                                var f5 = faces[5];
                                if (f5 === faces[1] || f5 === faces[2]) {
                                    faces[5] = faces[4];
                                    faces[4] = faces[3];
                                    faces[3] = f5;

                                    f5 = faces[5];
                                    if (f5 === faces[1] || f5 === faces[2]) {
                                        faces[5] = faces[4];
                                        faces[4] = faces[3];
                                        faces[3] = f5;
                                    }
                                }
                                if (faces[1] === faces[4] && faces[2] === faces[3]) {
                                    destSurface.primitive = gd.PRIMITIVE_TRIANGLE_STRIP;
                                    numIndices = 4;
                                    faces = [faces[0], faces[1], faces[2], faces[5]];
                                    destSurface.faces = faces;
                                }
                            }

                            if (!this._isSequentialIndices(faces, numIndices)) {
                                totalNumIndices += numIndices;
                            }
                        }
                    }
                }

                var indexBuffer, indexBufferData, indexBufferBaseIndex, indexBufferOffset, maxIndex;
                if (0 < totalNumIndices) {
                    maxIndex = (baseIndex + totalNumVertices - 1);
                    if (maxIndex >= 65536) {
                        if (totalNumVertices <= 65536) {
                            // Assign vertex offsets in blocks of 16bits so we can optimize renderables togheter
                            /* tslint:disable:no-bitwise */
                            var blockBase = ((baseIndex >>> 16) << 16);

                            /* tslint:enable:no-bitwise */
                            baseIndex -= blockBase;
                            if ((baseIndex + totalNumVertices) > 65536) {
                                blockBase += (baseIndex + totalNumVertices - 65536);
                                baseIndex = (65536 - totalNumVertices);
                                maxIndex = 65535;
                            } else {
                                maxIndex = (baseIndex + totalNumVertices - 1);
                            }
                            shape.vertexOffset = blockBase;
                        } else {
                            shape.vertexOffset = 0;
                        }
                    } else {
                        shape.vertexOffset = 0;
                    }

                    indexBufferAllocation = indexBufferManager.allocate(totalNumIndices, (maxIndex < 65536 ? 'USHORT' : 'UINT'));
                    indexBuffer = indexBufferAllocation.indexBuffer;
                    if (!indexBuffer) {
                        return undefined;
                    }

                    shape.indexBufferManager = indexBufferManager;
                    shape.indexBufferAllocation = indexBufferAllocation;

                    if (maxIndex < 65536 && this.uint16ArrayConstructor) {
                        indexBufferData = new this.uint16ArrayConstructor(totalNumIndices);
                    } else if (this.uint32ArrayConstructor) {
                        indexBufferData = new this.uint32ArrayConstructor(totalNumIndices);
                    } else {
                        indexBufferData = new Array(totalNumIndices);
                    }

                    indexBufferBaseIndex = indexBufferAllocation.baseIndex;
                    indexBufferOffset = 0;
                }

                for (s in surfaces) {
                    if (surfaces.hasOwnProperty(s)) {
                        destSurface = shape.surfaces[s];

                        faces = destSurface.faces;
                        delete destSurface.faces;

                        if (faces) {
                            // Vertices already de-indexed (1 index per vert)
                            numIndices = faces.length;

                            if (!this._isSequentialIndices(faces, numIndices)) {
                                destSurface.indexBuffer = indexBuffer;
                                destSurface.numIndices = numIndices;
                                destSurface.first = (indexBufferBaseIndex + indexBufferOffset);
                                destSurface.numVertices = totalNumVertices;

                                if (baseIndex) {
                                    for (t = 0; t < numIndices; t += 1) {
                                        indexBufferData[indexBufferOffset] = (baseIndex + faces[t]);
                                        indexBufferOffset += 1;
                                    }
                                } else {
                                    for (t = 0; t < numIndices; t += 1) {
                                        indexBufferData[indexBufferOffset] = faces[t];
                                        indexBufferOffset += 1;
                                    }
                                }

                                if (keepVertexData) {
                                    if (maxIndex < 65536 && this.uint16ArrayConstructor) {
                                        destSurface.indexData = new this.uint16ArrayConstructor(faces);
                                    } else if (this.uint32ArrayConstructor) {
                                        destSurface.indexData = new this.uint32ArrayConstructor(faces);
                                    } else {
                                        destSurface.indexData = faces;
                                    }
                                }
                            } else {
                                destSurface.first = (baseIndex + faces[0]);
                            }

                            faces = null;

                            if (keepVertexData) {
                                destSurface.vertexData = vertexData;
                            }
                        } else {
                            delete shape.surfaces[s];
                        }
                    }
                }

                if (indexBuffer) {
                    indexBuffer.setData(indexBufferData, indexBufferBaseIndex, totalNumIndices);
                    indexBufferData = null;
                }

                //Utilities.log("Buffers creation time: " + (TurbulenzEngine.time - startTime));
                var semanticsHash = semanticsNames.join();
                var semantics = cachedSemantics[semanticsHash];
                if (!semantics) {
                    semantics = gd.createSemantics(semanticsNames);
                    cachedSemantics[semanticsHash] = semantics;
                }
                shape.semantics = semantics;

                if (noSurfaces) {
                    // TODO: could remove this and always have surfaces
                    surface = shape.surfaces.singleSurface;

                    if (surface) {
                        shape.primitive = surface.primitive;
                        if (keepVertexData) {
                            shape.vertexData = surface.vertexData;
                        }

                        shape.first = surface.first;
                        shape.numVertices = surface.numVertices;

                        if (surface.indexBuffer) {
                            shape.indexBuffer = surface.indexBuffer;
                            shape.numIndices = surface.numIndices;
                            if (keepVertexData) {
                                shape.indexData = surface.indexData;
                            }
                        }
                    }

                    delete shape.surfaces;
                }
            }

            if (inputs.POSITION) {
                var positions = sources[inputs.POSITION.source];
                var minPos = positions.min;
                var maxPos = positions.max;
                if (minPos && maxPos) {
                    var min0 = minPos[0];
                    var min1 = minPos[1];
                    var min2 = minPos[2];
                    var max0 = maxPos[0];
                    var max1 = maxPos[1];
                    var max2 = maxPos[2];

                    var halfExtents, center;
                    if (min0 !== -max0 || min1 !== -max1 || min2 !== -max2) {
                        if (this.float32ArrayConstructor) {
                            var buffer = new this.float32ArrayConstructor(6);
                            center = buffer.subarray(0, 3);
                            halfExtents = buffer.subarray(3, 6);
                        } else {
                            center = new Array(3);
                            halfExtents = new Array(3);
                        }
                        center[0] = (min0 + max0) * 0.5;
                        center[1] = (min1 + max1) * 0.5;
                        center[2] = (min2 + max2) * 0.5;
                        halfExtents[0] = (max0 - center[0]);
                        halfExtents[1] = (max1 - center[1]);
                        halfExtents[2] = (max2 - center[2]);
                    } else {
                        halfExtents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(3) : new Array(3));
                        halfExtents[0] = (max0 - min0) * 0.5;
                        halfExtents[1] = (max1 - min1) * 0.5;
                        halfExtents[2] = (max2 - min2) * 0.5;
                    }
                    shape.center = center;
                    shape.halfExtents = halfExtents;
                }
                //else
                //{
                //TODO: add warning that we have no extents information
                //}
            }

            this.shapes[shapeName] = shape;
            shape.name = shapeName;
            shape.reference.subscribeDestroyed(this.onGeometryDestroyed);
        } else {
            throw "Geometry '" + shapeName + "' already exists in the scene";
        }
        return shape;
    };

    Scene.prototype.streamShapes = function (loadParams, postLoadFn) {
        // Firstly build an array listing all the shapes we need to load
        var yieldFn = loadParams.yieldFn;
        var scene = this;
        var shapesNamePrefix = loadParams.shapesNamePrefix;
        var sceneData = loadParams.data;
        var fileShapes = sceneData.geometries;
        var loadCustomShapeFn = loadParams.loadCustomShapeFn;

        var shapesToLoad = [];
        var customShapesToLoad = [];

        for (var fileShapeName in fileShapes) {
            if (fileShapes.hasOwnProperty(fileShapeName)) {
                var fileShape = fileShapes[fileShapeName];
                if (fileShape.meta && fileShape.meta.graphics) {
                    if (fileShape.meta.custom) {
                        customShapesToLoad.push(fileShapeName);
                    } else {
                        shapesToLoad.push(fileShapeName);
                    }
                }
            }
        }

        var sceneLoadNextShape = function sceneLoadNextShapeFn() {
            var nextShape = shapesToLoad.pop();

            var shapeName = (shapesNamePrefix ? (shapesNamePrefix + "-" + nextShape) : nextShape);
            scene.loadShape(shapeName, nextShape, loadParams);

            if (shapesToLoad.length) {
                yieldFn(sceneLoadNextShape);
            } else {
                yieldFn(postLoadFn);
            }
        };

        var sceneLoadNextCustomShape = function sceneLoadNextCustomShapeFn() {
            var nextShape = customShapesToLoad.pop();

            var shapeName = (shapesNamePrefix ? (shapesNamePrefix + "-" + nextShape) : nextShape);
            loadCustomShapeFn.call(scene, shapeName, nextShape, loadParams);

            if (customShapesToLoad.length) {
                yieldFn(sceneLoadNextCustomShape);
            } else if (shapesToLoad.length) {
                yieldFn(sceneLoadNextShape);
            } else {
                yieldFn(postLoadFn);
            }
        };

        if (customShapesToLoad.length) {
            yieldFn(sceneLoadNextCustomShape);
        } else if (shapesToLoad.length) {
            yieldFn(sceneLoadNextShape);
        } else {
            yieldFn(postLoadFn);
        }
    };

    //
    // Load lights
    //
    Scene.prototype.loadLights = function (loadParams) {
        var sceneData = loadParams.data;
        var textureManager = loadParams.textureManager;

        if (!loadParams.append) {
            this.lights = {};
            this.globalLights = [];
        }

        var fileLights = sceneData.lights;
        var lights = this.lights;
        var globalLights = this.globalLights;
        var materials = this.materials;
        var beget = Utilities.beget;

        var md = loadParams.mathDevice;
        var v3Build = md.v3Build;

        for (var l in fileLights) {
            if (fileLights.hasOwnProperty(l) && !lights[l]) {
                var fileLight = fileLights[l];

                // convert to create parameters
                var lightParams = beget(fileLight);

                var type = fileLight.type;
                if (type === 'directional') {
                    lightParams.directional = true;
                } else if (type === 'spot') {
                    lightParams.spot = true;
                } else if (type === 'ambient') {
                    lightParams.ambient = true;
                } else {
                    lightParams.point = true;
                }

                // Convert to MathDevice objects
                lightParams.color = fileLight.color && v3Build.apply(md, fileLight.color);

                lightParams.origin = fileLight.origin && v3Build.apply(md, fileLight.origin);
                lightParams.center = fileLight.center && v3Build.apply(md, fileLight.center);
                lightParams.target = fileLight.target && v3Build.apply(md, fileLight.target);
                lightParams.right = fileLight.right && v3Build.apply(md, fileLight.right);
                lightParams.up = fileLight.up && v3Build.apply(md, fileLight.up);
                lightParams.start = fileLight.start && v3Build.apply(md, fileLight.start);
                lightParams.end = fileLight.end && v3Build.apply(md, fileLight.end);
                lightParams.direction = fileLight.direction && v3Build.apply(md, fileLight.direction);

                lightParams.halfExtents = fileLight.halfextents && v3Build.apply(md, fileLight.halfextents);

                var materialName = fileLight.material;
                if (materialName) {
                    var material = materials[materialName];
                    if (material) {
                        lightParams.material = material;

                        if (material.effectName) {
                            delete material.effectName;
                            material.loadTextures(textureManager);
                        }
                    }
                }

                var light = Light.create(lightParams);
                lights[l] = light;
                if (light.isGlobal()) {
                    globalLights.push(light);
                }
            }
        }
    };

    //
    // loadNodes
    //
    Scene.prototype.loadNodes = function (loadParams) {
        var sceneData = loadParams.data;
        var gd = loadParams.graphicsDevice;
        var textureManager = loadParams.textureManager;
        var effectManager = loadParams.effectManager;
        var baseScene = loadParams.baseScene;
        var keepCameras = loadParams.keepCameras;
        var keepLights = loadParams.keepLights;
        var optimizeHierarchy = loadParams.optimizeHierarchy;
        var optimizeRenderables = loadParams.optimizeRenderables;
        var disableNodes = loadParams.disabled;

        if (!loadParams.append) {
            this.clearRootNodes();
            this.staticSpatialMap.clear();
            this.dynamicSpatialMap.clear();
        }

        var loadCustomGeometryInstanceFn = loadParams.loadCustomGeometryInstanceFn;

        var md = this.md;
        var m43Build = md.m43Build;
        var materials = this.materials;
        var lights = this.lights;
        var currentScene = this;

        var baseMaterials;
        if (baseScene) {
            baseMaterials = baseScene.materials;
        }
        var baseMatrix = loadParams.baseMatrix;
        var nodesNamePrefix = loadParams.nodesNamePrefix;
        var shapesNamePrefix = loadParams.shapesNamePrefix;

        function optimizeNode(parent, child) {
            function matrixIsIdentity(matrix) {
                var abs = Math.abs;
                return (abs(1.0 - matrix[0]) < 1e-5 && abs(0.0 - matrix[1]) < 1e-5 && abs(0.0 - matrix[2]) < 1e-5 && abs(0.0 - matrix[3]) < 1e-5 && abs(1.0 - matrix[4]) < 1e-5 && abs(0.0 - matrix[5]) < 1e-5 && abs(0.0 - matrix[6]) < 1e-5 && abs(0.0 - matrix[7]) < 1e-5 && abs(1.0 - matrix[8]) < 1e-5 && abs(0.0 - matrix[9]) < 1e-5 && abs(0.0 - matrix[10]) < 1e-5 && abs(0.0 - matrix[11]) < 1e-5);
            }

            if ((!child.camera || !parent.camera) && child.disabled === parent.disabled && child.dynamic === parent.dynamic && child.kinematic === parent.kinematic && (!child.local || matrixIsIdentity(child.local))) {
                if (child.renderables) {
                    parent.addRenderableArray(child.renderables);
                }

                if (child.lightInstances) {
                    parent.addLightInstanceArray(child.lightInstances);
                }

                if (child.camera) {
                    parent.camera = child.camera;
                }

                var grandChildren = child.children;
                if (grandChildren) {
                    var n;
                    var numGrandChildren = grandChildren;
                    for (n = 0; n < numGrandChildren; n += 1) {
                        if (!optimizeNode(parent, child)) {
                            parent.addChild(child);
                        }
                    }
                }
                return true;
            }

            return false;
        }

        var copyNode = function copyNodeFn(nodeName, parentNodePath, baseNode, materialSkin) {
            var nodePath = parentNodePath ? (parentNodePath + "/" + nodeName) : nodeName;

            var node = SceneNode.create({
                name: nodeName,
                local: this.matrix && m43Build.apply(md, this.matrix),
                dynamic: this.dynamic || baseNode.dynamic || loadParams.dynamic
            });

            var effect;

            var customgeometryinstance = this.customgeometryinstances;
            if (customgeometryinstance && loadCustomGeometryInstanceFn) {
                for (var ci in customgeometryinstance) {
                    if (customgeometryinstance.hasOwnProperty(ci)) {
                        var fileCustomGeometryInstance = customgeometryinstance[ci];
                        var customGeometryInstance = loadCustomGeometryInstanceFn.call(currentScene, fileCustomGeometryInstance, loadParams);

                        if (customGeometryInstance) {
                            node.addRenderable(customGeometryInstance);
                        }
                    }
                }
            }

            var geometryinstances = this.geometryinstances;
            if (geometryinstances) {
                for (var gi in geometryinstances) {
                    if (geometryinstances.hasOwnProperty(gi)) {
                        var fileGeometryInstance = geometryinstances[gi];
                        var fileShapeName = fileGeometryInstance.geometry;
                        var shapeName = (shapesNamePrefix ? (shapesNamePrefix + "-" + fileShapeName) : fileShapeName);

                        // If the geometry has already been loaded,
                        // use that, otherwise attempt to load it from
                        // the current set of parameters.
                        var nodeShape = currentScene.shapes[shapeName];
                        if (!nodeShape) {
                            nodeShape = currentScene.loadShape(shapeName, fileShapeName, loadParams);
                        }

                        if (gd) {
                            var sharedMaterialName = fileGeometryInstance.material;
                            if (materialSkin && sceneData.skins) {
                                var skin = sceneData.skins[materialSkin];
                                if (skin) {
                                    var newMaterialName = skin[sharedMaterialName];
                                    if (newMaterialName) {
                                        sharedMaterialName = newMaterialName;
                                    }
                                }
                            }
                            var sharedMaterial = materials[sharedMaterialName];
                            if (!sharedMaterial) {
                                if (baseMaterials) {
                                    sharedMaterial = baseMaterials[sharedMaterialName];
                                }

                                if (!sharedMaterial) {
                                    //Utilities.log("Unknown material '" + sharedMaterialName + "'");
                                    return undefined;
                                }
                                materials[sharedMaterialName] = sharedMaterial;
                                sharedMaterial.name = sharedMaterialName;
                                sharedMaterial.reference.subscribeDestroyed(currentScene.onMaterialDestroyed);
                            }
                            effect = sharedMaterial.effect;
                            if (!effect) {
                                // Load the textures since if the effect is undefined then scene.loadMaterial
                                // has not yet been called for this material
                                sharedMaterial.loadTextures(textureManager);
                                var effectName = sharedMaterial.effectName;
                                delete sharedMaterial.effectName;
                                effect = effectManager.get(effectName);
                                if (effect) {
                                    effect.prepareMaterial(sharedMaterial);
                                }
                            }

                            var surfaces = nodeShape.surfaces;
                            var surface = (surfaces ? surfaces[fileGeometryInstance.surface] : nodeShape);

                            var geometryInstance = GeometryInstance.create(nodeShape, surface, sharedMaterial);
                            node.addRenderable(geometryInstance);

                            if (fileGeometryInstance.disabled) {
                                geometryInstance.disabled = true;
                            }
                        } else {
                            // TODO: TSC complains about this,
                            // apparenty for good reason.
                            node.addRenderable(GeometryInstance.create(nodeShape, null, null));
                        }
                    }
                }
            }

            if (this.camera) {
                if (keepCameras) {
                    node.camera = this.camera;
                }
            }

            // Check for any instances of lights attached to the node
            var fileLightInstances = this.lightinstances;
            if (fileLightInstances && keepLights) {
                for (var li in fileLightInstances) {
                    if (fileLightInstances.hasOwnProperty(li)) {
                        var fileLightInstance = fileLightInstances[li];
                        var light = lights[fileLightInstance.light];
                        if (light && !light.global) {
                            var lightInstance = LightInstance.create(light);
                            node.addLightInstance(lightInstance);
                            if (fileLightInstance.disabled) {
                                lightInstance.disabled = true;
                            }
                        }
                    }
                }
            }

            if (this.reference) {
                alert("Found unresolved node reference during scene loading");
            }

            if (this.kinematic || baseNode.kinematic) {
                node.kinematic = true;
            }

            if ((this.disabled || baseNode.disabled) && (disableNodes !== false)) {
                node.disabled = true;
            }

            var fileChildren = this.nodes;
            if (fileChildren) {
                for (var c in fileChildren) {
                    if (fileChildren.hasOwnProperty(c)) {
                        if (!node.findChild(c)) {
                            var child = copyNode.call(fileChildren[c], c, nodePath, node, this.skin || materialSkin);
                            if (child) {
                                if (!optimizeHierarchy || !optimizeNode(node, child)) {
                                    node.addChild(child);
                                }
                            }
                        }
                    }
                }
            }

            if (optimizeRenderables) {
                if (node.renderables && 1 < node.renderables.length) {
                    currentScene._optimizeRenderables(node, gd);
                }
            }

            return node;
        };

        var fileNodes = sceneData.nodes;
        var parentNode = loadParams.parentNode;

        var emptyNode = {};
        for (var fn in fileNodes) {
            if (fileNodes.hasOwnProperty(fn)) {
                var fileNode = fileNodes[fn];
                var nodeName = fn;
                var nodePath = (nodesNamePrefix ? (nodesNamePrefix + "/" + fn) : fn);
                var overloadedNode = currentScene.findNode(nodePath);

                var node = copyNode.call(fileNode, nodeName, nodesNamePrefix, (overloadedNode || parentNode || emptyNode), fileNode.skin || loadParams.materialSkin);
                if (node) {
                    if (parentNode && !overloadedNode) {
                        parentNode.addChild(node);
                    }

                    if (baseMatrix) {
                        if (node.local) {
                            node.setLocalTransform(md.m43Mul(node.getLocalTransform(), baseMatrix));
                        } else {
                            node.setLocalTransform(baseMatrix);
                        }
                    } else {
                        if (!node.local) {
                            node.setLocalTransform(md.m43BuildIdentity());
                        }
                    }

                    if (disableNodes) {
                        node.enableHierarchy(false);
                    }

                    if (overloadedNode) {
                        //Utilities.log("Overloaded node '" + nodePath + "'");
                        var overloadedMatrix = overloadedNode.local;
                        if (overloadedMatrix && node.local) {
                            node.local = md.m43Mul(node.local, overloadedMatrix);
                            overloadedNode.setLocalTransform(node.local);
                            delete node.local;
                        }

                        var overloadedChildren = overloadedNode.children;
                        if (overloadedChildren && node.children) {
                            while (node.children.length) {
                                var child = node.children[0];
                                if (!overloadedNode.findChild(child.name)) {
                                    overloadedNode.addChild(child);
                                }
                                node.removeChild(child);
                            }
                        }

                        for (var on in node) {
                            if (node.hasOwnProperty(on)) {
                                overloadedNode[on] = node[on];
                            }
                        }
                        node = null;
                    } else if (!parentNode) {
                        this.addRootNode(node);
                    }
                }
            }
        }
    };

    //
    // loadAreas
    //
    Scene.prototype.loadAreas = function (loadParams) {
        var sceneData = loadParams.data;

        var fileAreas = sceneData.areas;
        if (!fileAreas) {
            return;
        }

        var numFileAreas = fileAreas.length;
        if (numFileAreas <= 0) {
            return;
        }

        if (!loadParams.append) {
            delete this.areas;
        }

        var areas = this.areas;
        if (!areas) {
            areas = [];
            this.areas = areas;
        }

        var nodesNamePrefix = loadParams.nodesNamePrefix;
        var md = this.md;
        var planeNormalize = this.planeNormalize;
        var baseIndex = areas.length;

        var maxValue = Number.MAX_VALUE;
        var buffer, bufferIndex;

        for (var fa = 0; fa < numFileAreas; fa += 1) {
            var fileArea = fileAreas[fa];

            var targetName = fileArea.target;
            if (nodesNamePrefix) {
                targetName = (nodesNamePrefix + "/" + targetName);
            }
            var target = this.findNode(targetName);
            if (!target) {
                //Utilities.log("Missing target: " + targetName);
                baseIndex -= 1;
                continue;
            }

            var matrix = target.getWorldTransform();
            var m0 = matrix[0];
            var m1 = matrix[1];
            var m2 = matrix[2];
            var m3 = matrix[3];
            var m4 = matrix[4];
            var m5 = matrix[5];
            var m6 = matrix[6];
            var m7 = matrix[7];
            var m8 = matrix[8];
            var m9 = matrix[9];
            var m10 = matrix[10];
            var m11 = matrix[11];

            var minAreaX = maxValue;
            var minAreaY = maxValue;
            var minAreaZ = maxValue;
            var maxAreaX = -maxValue;
            var maxAreaY = -maxValue;
            var maxAreaZ = -maxValue;

            var filePortals = fileArea.portals;
            var numFilePortals = filePortals.length;
            var portals = [];
            var filePortal, filePoints, points, numPoints, np, filePoint;
            var areaExtents;

            if (this.float32ArrayConstructor) {
                buffer = new this.float32ArrayConstructor(6 + (numFilePortals * (6 + 3 + 4)));
                bufferIndex = 0;

                areaExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                bufferIndex += 6;
            } else {
                areaExtents = new Array(6);
            }

            for (var fp = 0; fp < numFilePortals; fp += 1) {
                var minX = maxValue;
                var minY = maxValue;
                var minZ = maxValue;
                var maxX = -maxValue;
                var maxY = -maxValue;
                var maxZ = -maxValue;
                var c0 = 0;
                var c1 = 0;
                var c2 = 0;
                filePortal = filePortals[fp];
                filePoints = filePortal.points;
                numPoints = filePoints.length;
                points = [];
                for (np = 0; np < numPoints; np += 1) {
                    filePoint = filePoints[np];
                    var fp0 = filePoint[0];
                    var fp1 = filePoint[1];
                    var fp2 = filePoint[2];
                    var p0 = (m0 * fp0 + m3 * fp1 + m6 * fp2 + m9);
                    var p1 = (m1 * fp0 + m4 * fp1 + m7 * fp2 + m10);
                    var p2 = (m2 * fp0 + m5 * fp1 + m8 * fp2 + m11);
                    if (p0 < minX) {
                        minX = p0;
                    }
                    if (p1 < minY) {
                        minY = p1;
                    }
                    if (p2 < minZ) {
                        minZ = p2;
                    }
                    if (p0 > maxX) {
                        maxX = p0;
                    }
                    if (p1 > maxY) {
                        maxY = p1;
                    }
                    if (p2 > maxZ) {
                        maxZ = p2;
                    }
                    c0 += p0;
                    c1 += p1;
                    c2 += p2;
                    points.push(md.v3Build(p0, p1, p2));
                }
                if (minX < minAreaX) {
                    minAreaX = minX;
                }
                if (minY < minAreaY) {
                    minAreaY = minY;
                }
                if (minZ < minAreaZ) {
                    minAreaZ = minZ;
                }
                if (maxX > maxAreaX) {
                    maxAreaX = maxX;
                }
                if (maxY > maxAreaY) {
                    maxAreaY = maxY;
                }
                if (maxZ > maxAreaZ) {
                    maxAreaZ = maxZ;
                }
                var normal = md.v3Cross(md.v3Sub(points[1], points[0]), md.v3Sub(points[2], points[0]));

                var portalExtents, portalOrigin, portalPlane;
                if (this.float32ArrayConstructor) {
                    portalExtents = buffer.subarray(bufferIndex, (bufferIndex + 6));
                    bufferIndex += 6;
                    portalOrigin = buffer.subarray(bufferIndex, (bufferIndex + 3));
                    bufferIndex += 3;
                    portalPlane = buffer.subarray(bufferIndex, (bufferIndex + 4));
                    bufferIndex += 4;
                } else {
                    portalExtents = new Array(6);
                    portalOrigin = new Array(3);
                    portalPlane = new Array(4);
                }
                portalExtents[0] = minX;
                portalExtents[1] = minY;
                portalExtents[2] = minZ;
                portalExtents[3] = maxX;
                portalExtents[4] = maxY;
                portalExtents[5] = maxZ;

                portalOrigin[0] = (c0 / numPoints);
                portalOrigin[1] = (c1 / numPoints);
                portalOrigin[2] = (c2 / numPoints);

                portalPlane = planeNormalize(normal[0], normal[1], normal[2], md.v3Dot(normal, points[0]), portalPlane);

                var portal = {
                    area: (baseIndex + filePortal.area),
                    points: points,
                    origin: portalOrigin,
                    extents: portalExtents,
                    plane: portalPlane
                };
                portals.push(portal);
            }

            areaExtents[0] = minAreaX;
            areaExtents[1] = minAreaY;
            areaExtents[2] = minAreaZ;
            areaExtents[3] = maxAreaX;
            areaExtents[4] = maxAreaY;
            areaExtents[5] = maxAreaZ;

            var area = {
                target: target,
                portals: portals,
                extents: areaExtents,
                externalNodes: null
            };
            areas.push(area);
        }

        // Keep bsp tree
        var fileBspNodes = sceneData.bspnodes;
        var numBspNodes = fileBspNodes.length;
        var bspNodes = [];
        bspNodes.length = numBspNodes;
        this.bspNodes = bspNodes;

        if (this.float32ArrayConstructor) {
            buffer = new this.float32ArrayConstructor(4 * numBspNodes);
            bufferIndex = 0;
        }

        for (var bn = 0; bn < numBspNodes; bn += 1) {
            var fileBspNode = fileBspNodes[bn];
            var plane = fileBspNode.plane;
            var nodePlane;
            if (this.float32ArrayConstructor) {
                nodePlane = buffer.subarray(bufferIndex, (bufferIndex + 4));
                bufferIndex += 4;
            } else {
                nodePlane = new Array(4);
            }
            nodePlane[0] = plane[0];
            nodePlane[1] = plane[1];
            nodePlane[2] = plane[2];
            nodePlane[3] = -plane[3];
            bspNodes[bn] = {
                plane: nodePlane,
                pos: fileBspNode.pos,
                neg: fileBspNode.neg
            };
        }
    };

    //
    // load
    //
    Scene.prototype.load = function (loadParams) {
        var scene = this;

        if (!loadParams.append) {
            this.clearShapes();
            this.semantics = {};
        }

        var sceneCompleteLoadStage = function sceneCompleteLoadStageFn() {
            if (loadParams.keepLights) {
                scene.loadLights(loadParams);
            }

            scene.loadNodes(loadParams);

            if (loadParams.physicsManager) {
                loadParams.physicsManager.loadNodes(loadParams, scene);
            }

            scene.loadAreas(loadParams);

            scene.endLoading(loadParams.onload);
        };

        if (loadParams.graphicsDevice) {
            this.loadMaterials(loadParams);
        }

        // Needs to be called before the geometry is loaded by loadNodes or streamShapes
        scene.loadSkeletons(loadParams);

        var yieldFn = loadParams.yieldFn;
        if (yieldFn) {
            var streamNodesStage = function sceneStreamNodesStage() {
                scene.streamShapes(loadParams, sceneCompleteLoadStage);
            };
            yieldFn(streamNodesStage);
        } else {
            sceneCompleteLoadStage();
        }
    };

    Scene.prototype.planeNormalize = function (a, b, c, d, dst) {
        var res = dst;
        if (!res) {
            /*jshint newcap: false*/
            var float32ArrayConstructor = Scene.prototype.float32ArrayConstructor;
            res = (float32ArrayConstructor ? new float32ArrayConstructor(4) : new Array(4));
            /*jshint newcap: true*/
        }

        var lsq = ((a * a) + (b * b) + (c * c));
        if (lsq > 0.0) {
            var lr = 1.0 / Math.sqrt(lsq);
            res[0] = (a * lr);
            res[1] = (b * lr);
            res[2] = (c * lr);
            res[3] = (d * lr);
        } else {
            res[0] = 0;
            res[1] = 0;
            res[2] = 0;
            res[3] = 0;
        }

        return res;
    };

    Scene.prototype.isInsidePlanesAABB = function (extents, planes) {
        var n0 = extents[0];
        var n1 = extents[1];
        var n2 = extents[2];
        var p0 = extents[3];
        var p1 = extents[4];
        var p2 = extents[5];
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            if ((d0 * (d0 < 0 ? n0 : p0) + d1 * (d1 < 0 ? n1 : p1) + d2 * (d2 < 0 ? n2 : p2)) < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    };

    Scene.prototype.isFullyInsidePlanesAABB = function (extents, planes) {
        var n0 = extents[0];
        var n1 = extents[1];
        var n2 = extents[2];
        var p0 = extents[3];
        var p1 = extents[4];
        var p2 = extents[5];
        var numPlanes = planes.length;
        var n = 0;
        do {
            var plane = planes[n];
            var d0 = plane[0];
            var d1 = plane[1];
            var d2 = plane[2];
            if ((d0 * (d0 > 0 ? n0 : p0) + d1 * (d1 > 0 ? n1 : p1) + d2 * (d2 > 0 ? n2 : p2)) < plane[3]) {
                return false;
            }
            n += 1;
        } while(n < numPlanes);
        return true;
    };

    Scene.prototype.extractFrustumPlanes = function (camera) {
        var planeNormalize = this.planeNormalize;
        var m = camera.viewProjectionMatrix;
        var m0 = m[0];
        var m1 = m[1];
        var m2 = m[2];
        var m3 = m[3];
        var m4 = m[4];
        var m5 = m[5];
        var m6 = m[6];
        var m7 = m[7];
        var m8 = m[8];
        var m9 = m[9];
        var m10 = m[10];
        var m11 = m[11];
        var m12 = m[12];
        var m13 = m[13];
        var m14 = m[14];
        var m15 = m[15];
        var planes = this.frustumPlanes;

        // Negate 'd' here to avoid doing it on the isVisible functions
        planes[0] = planeNormalize((m3 + m0), (m7 + m4), (m11 + m8), -(m15 + m12), planes[0]);
        planes[1] = planeNormalize((m3 - m0), (m7 - m4), (m11 - m8), -(m15 - m12), planes[1]);
        planes[2] = planeNormalize((m3 - m1), (m7 - m5), (m11 - m9), -(m15 - m13), planes[2]);
        planes[3] = planeNormalize((m3 + m1), (m7 + m5), (m11 + m9), -(m15 + m13), planes[3]);

        if (this.areas) {
            if (planes.length > 4) {
                planes.length = 4;
            }
        } else {
            planes[4] = planeNormalize((m3 - m2), (m7 - m6), (m11 - m10), -(m15 - m14), planes[4]);
        }

        this.nearPlane = planeNormalize((m3 + m2), (m7 + m6), (m11 + m10), -(m15 + m14), this.nearPlane);

        return planes;
    };

    //
    // calculateHullScreenExtents
    //
    Scene.prototype.calculateHullScreenExtents = function (polygons, screenExtents) {
        // Sutherland-Hodgman polygon clipping algorithm
        var clipLine = function clipLineFn(va, vb, axis, positive, out) {
            var a = va[axis];
            var b = vb[axis];
            var aw = va[3];
            var bw = vb[3];
            var t = 0.0;
            var bInside = true;
            if (positive) {
                if (a > aw) {
                    if (b <= bw) {
                        if (b < bw) {
                            t = ((aw - a) / ((b - a) - (bw - aw)));
                        }
                    } else {
                        // both out
                        return;
                    }
                } else if (b > bw) {
                    if (a < aw) {
                        t = ((aw - a) / ((b - a) - (bw - aw)));
                    }
                    bInside = false;
                }
            } else {
                if (a < -aw) {
                    if (b >= -bw) {
                        if (b > -bw) {
                            t = ((-aw - a) / ((b - a) + (bw - aw)));
                        }
                    } else {
                        // both out
                        return;
                    }
                } else if (b < -bw) {
                    if (a > -aw) {
                        t = ((-aw - a) / ((b - a) + (bw - aw)));
                    }
                    bInside = false;
                }
            }

            if (t > 0.0) {
                var ax = va[0];
                var ay = va[1];
                var az = va[2];
                var bx = vb[0];
                var by = vb[1];
                var bz = vb[2];
                out.push([
                    (ax + (t * (bx - ax))),
                    (ay + (t * (by - ay))),
                    (az + (t * (bz - az))),
                    (aw + (t * (bw - aw)))
                ]);
            }

            if (bInside) {
                out.push(vb);
            }
        };

        var minX = 1.0;
        var maxX = -1.0;
        var minY = 1.0;
        var maxY = -1.0;

        var numPolygons = polygons.length;
        for (var n = 0; n < numPolygons; n += 1) {
            var points = polygons[n];
            var numPoints, p, a, b, out;
            for (var positive = 0; positive < 2; positive += 1) {
                for (var axis = 0; axis < 3; axis += 1) {
                    numPoints = points.length;
                    if (!numPoints) {
                        break;
                    }
                    out = [];
                    for (p = 0; p < numPoints; p += 1) {
                        if (p < 1) {
                            a = points[numPoints - 1];
                        } else {
                            a = points[p - 1];
                        }
                        b = points[p];
                        clipLine(a, b, axis, positive, out);
                    }
                    points = out;
                }
            }

            numPoints = points.length;
            for (p = 0; p < numPoints; p += 1) {
                a = points[p];
                var ax = a[0];
                var ay = a[1];
                var aw = a[3];
                if (aw === 0) {
                    ax = (ax >= 0 ? 1 : -1);
                    ay = (ay >= 0 ? 1 : -1);
                } else {
                    var rcpa = 1.0 / aw;
                    ax *= rcpa;
                    ay *= rcpa;
                }
                if (minX > ax) {
                    minX = ax;
                }
                if (maxX < ax) {
                    maxX = ax;
                }
                if (minY > ay) {
                    minY = ay;
                }
                if (maxY < ay) {
                    maxY = ay;
                }
            }
        }

        if (minX >= maxX || minY >= maxY) {
            return undefined;
        }

        if (minX < -1.0) {
            minX = -1.0;
        }
        if (maxX > 1.0) {
            maxX = 1.0;
        }
        if (minY < -1.0) {
            minY = -1.0;
        }
        if (maxY > 1.0) {
            maxY = 1.0;
        }

        if (!screenExtents) {
            screenExtents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(4) : new Array(4));
        }
        screenExtents[0] = minX;
        screenExtents[1] = minY;
        screenExtents[2] = maxX;
        screenExtents[3] = maxY;
        return screenExtents;
    };

    //
    // calculateLightsScreenExtents
    //
    Scene.prototype.calculateLightsScreenExtents = function (camera) {
        var visibleLights = this.visibleLights;
        var numVisibleLights = visibleLights.length;
        if (numVisibleLights > 0) {
            var matrix, transform, halfExtents, center, hx, hy, hz, p0, p1, p2, p3, p4, p5, p6, p7, st, polygons;
            var lightInstance, light, worldViewProjectionMatrix;
            var viewProjectionMatrix = camera.viewProjectionMatrix;
            var calculateHullScreenExtents = this.calculateHullScreenExtents;
            var md = this.md;
            var m44Transform = md.m44Transform;
            var m43MulM44 = md.m43MulM44;
            var v4Build = md.v4Build;
            var spotA = v4Build.call(md, -1, -1, 1, 1);
            var spotB = v4Build.call(md, 1, -1, 1, 1);
            var spotC = v4Build.call(md, -1, 1, 1, 1);
            var spotD = v4Build.call(md, 1, 1, 1, 1);
            var n = 0;
            do {
                lightInstance = visibleLights[n];
                light = lightInstance.light;
                if (light) {
                    if (light.global) {
                        continue;
                    }

                    matrix = lightInstance.node.world;

                    if (light.spot) {
                        transform = md.m33MulM43(light.frustum, matrix, transform);

                        worldViewProjectionMatrix = m43MulM44.call(md, transform, viewProjectionMatrix, worldViewProjectionMatrix);

                        p0 = m44Transform.call(md, worldViewProjectionMatrix, spotA, p0);
                        p1 = m44Transform.call(md, worldViewProjectionMatrix, spotB, p1);
                        p2 = m44Transform.call(md, worldViewProjectionMatrix, spotC, p2);
                        p3 = m44Transform.call(md, worldViewProjectionMatrix, spotD, p3);

                        st = v4Build.call(md, matrix[9], matrix[10], matrix[11], 1, st);
                        st = m44Transform.call(md, viewProjectionMatrix, st, st);

                        polygons = [
                            [st, p0, p1],
                            [st, p1, p3],
                            [st, p2, p0],
                            [st, p3, p2],
                            [p2, p3, p1, p0]
                        ];
                    } else {
                        halfExtents = light.halfExtents;
                        if (!light.fog) {
                            center = light.center;
                            if (center) {
                                matrix = transform = md.m43Offset(matrix, center, transform);
                            }
                        }

                        hx = halfExtents[0];
                        hy = halfExtents[1];
                        hz = halfExtents[2];

                        worldViewProjectionMatrix = m43MulM44.call(md, matrix, viewProjectionMatrix, worldViewProjectionMatrix);

                        p0 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, -hx, -hy, -hz, 1, p0), p0);
                        p1 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, +hx, -hy, -hz, 1, p1), p1);
                        p2 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, +hx, -hy, +hz, 1, p2), p2);
                        p3 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, -hx, -hy, +hz, 1, p3), p3);
                        p4 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, -hx, +hy, -hz, 1, p4), p4);
                        p5 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, +hx, +hy, -hz, 1, p5), p5);
                        p6 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, +hx, +hy, +hz, 1, p6), p6);
                        p7 = m44Transform.call(md, worldViewProjectionMatrix, v4Build.call(md, -hx, +hy, +hz, 1, p7), p7);

                        polygons = [
                            [p3, p2, p1, p0],
                            [p4, p5, p6, p7],
                            [p0, p1, p5, p4],
                            [p7, p6, p2, p3],
                            [p4, p7, p3, p0],
                            [p1, p2, p6, p5]
                        ];
                    }

                    lightInstance.screenExtents = calculateHullScreenExtents(polygons, lightInstance.screenExtents);
                }

                n += 1;
            } while(n < numVisibleLights);
        }
    };

    //
    // destroy
    //
    Scene.prototype.destroy = function () {
        this.clear();
        if (this.vertexBufferManager) {
            this.vertexBufferManager.destroy();
            delete this.vertexBufferManager;
        }
        if (this.indexBufferManager) {
            this.indexBufferManager.destroy();
            delete this.indexBufferManager;
        }
    };

    Scene.prototype.getQueryCounter = function () {
        var queryCounter = this.queryCounter;
        this.queryCounter = (queryCounter + 1);
        return queryCounter;
    };

    Scene.create = // Constructor function
    function (mathDevice, staticSpatialMap, dynamicSpatialMap) {
        return new Scene(mathDevice, staticSpatialMap, dynamicSpatialMap);
    };
    Scene.version = 1;
    return Scene;
})();

// Detect correct typed arrays
((function () {
    var testArray, textDescriptor;
    if (typeof Uint16Array !== "undefined") {
        testArray = new Uint16Array(4);
        textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Uint16Array]') {
            Scene.prototype.uint16ArrayConstructor = Uint16Array;
        }
    }
    if (typeof Uint32Array !== "undefined") {
        testArray = new Uint32Array(4);
        textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Uint32Array]') {
            Scene.prototype.uint32ArrayConstructor = Uint32Array;
        }
    }
    if (typeof Float32Array !== "undefined") {
        testArray = new Float32Array(4);
        textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            Scene.prototype.float32ArrayConstructor = Float32Array;
        }
    }
})());


// Copyright (c) 2009-2014 Turbulenz Limited
/*global Utilities: false*/

;

//
// Effect
//
var Effect = (function () {
    function Effect() {
    }
    Effect.create = function (name) {
        var effect = new Effect();

        effect.name = name;
        effect.geometryType = {};
        effect.numMaterials = 0;
        effect.materialsMap = {};

        return effect;
    };

    Effect.prototype.hashMaterial = function (material) {
        var texturesNames = material.texturesNames;
        var hashArray = [];
        var numTextures = 0;
        for (var p in texturesNames) {
            if (texturesNames.hasOwnProperty(p)) {
                hashArray[numTextures] = texturesNames[p];
                numTextures += 1;
            }
        }
        if (1 < numTextures) {
            hashArray.sort();
            return hashArray.join(',');
        } else if (0 < numTextures) {
            return hashArray[0];
        } else {
            /* tslint:disable:no-string-literal */
            var materialColor = material.techniqueParameters['materialColor'];

            if (materialColor) {
                var length = materialColor.length;
                var n;
                for (n = 0; n < length; n += 1) {
                    hashArray[n] = materialColor[n].toFixed(3).replace('.000', '');
                }
                return hashArray.join(',');
            } else {
                return material.name;
            }
        }
    };

    Effect.prototype.prepareMaterial = function (material) {
        var hash = this.hashMaterial(material);
        var index = this.materialsMap[hash];
        if (index === undefined) {
            index = this.numMaterials;
            this.numMaterials += 1;
            this.materialsMap[hash] = index;
        }
        material.meta.materialIndex = index;
        material.effect = this;
    };

    Effect.prototype.add = function (geometryType, prepareObject) {
        this.geometryType[geometryType] = prepareObject;
    };

    Effect.prototype.remove = function (geometryType) {
        delete this.geometryType[geometryType];
    };

    Effect.prototype.get = function (geometryType) {
        return this.geometryType[geometryType];
    };

    Effect.prototype.prepare = function (renderable) {
        var prepareObject = this.geometryType[renderable.geometryType];
        if (prepareObject) {
            prepareObject.prepare(renderable);
        } else {
            /* debug.abort("Unsupported or missing geometryType"); */
        }
    };
    Effect.version = 1;
    return Effect;
})();

//
// EffectManager
//
var EffectManager = (function () {
    function EffectManager() {
    }
    EffectManager.create = function () {
        var effectManager = new EffectManager();
        effectManager.effects = {};
        return effectManager;
    };

    EffectManager.prototype.add = function (effect) {
        /* debug.assert(this.effects[effect.name] === undefined); */
        this.effects[effect.name] = effect;
    };

    EffectManager.prototype.remove = function (name) {
        delete this.effects[name];
    };

    EffectManager.prototype.map = function (destination, source) {
        this.effects[destination] = this.effects[source];
    };

    EffectManager.prototype.get = function (name) {
        var effect = this.effects[name];
        if (!effect) {
            /* tslint:disable:no-string-literal */
            return this.effects["default"];
            /* tslint:enable:no-string-literal */
        }
        return effect;
    };
    EffectManager.version = 1;
    return EffectManager;
})();

// Copyright (c) 2009-2014 Turbulenz Limited
/*global Observer: false*/
/*global TurbulenzEngine: false*/

//
// ShaderManager
//
var ShaderManager = (function () {
    function ShaderManager() {
    }
    ShaderManager.prototype.get = function (path) {
        /* debug.abort("abstract method"); */
        return null;
    };

    ShaderManager.create = /**
    @constructs Constructs a ShaderManager object.
    
    @param {GraphicsDevice} gd Graphics device
    @param {RequestHandler} rh RequestHandler device
    @param {Shader} ds Default shader
    @param {Element} log Logging element
    
    @return {ShaderManager} object, null if failed
    */
    function (gd, rh, ds, errorCallback, log) {
        if (!errorCallback) {
            errorCallback = function (/* e */ ) {
            };
        }

        var defaultShaderName = "default";

        var defaultShader;
        if (ds) {
            defaultShader = ds;
        } else {
            var shaderParams = {
                "version": 1,
                "name": "default.cgfx",
                "parameters": {
                    "worldViewProjection": {
                        "type": "float",
                        "rows": 4,
                        "columns": 4
                    },
                    "diffuse": {
                        "type": "sampler2D"
                    }
                },
                "techniques": {
                    "textured3D": [
                        {
                            "parameters": ["worldViewProjection", "diffuse"],
                            "semantics": ["POSITION", "TEXCOORD0"],
                            "states": {
                                "DepthTestEnable": true,
                                "DepthFunc": 515,
                                "DepthMask": true,
                                "CullFaceEnable": true,
                                "CullFace": 1029,
                                "BlendEnable": false
                            },
                            "programs": ["vp", "fp"]
                        }
                    ]
                },
                "programs": {
                    "fp": {
                        "type": "fragment",
                        "code": "#ifdef GL_ES\nprecision mediump float;precision mediump int;\n#endif\nvarying vec4 tz_TexCoord[1];vec4 _ret_0;uniform sampler2D diffuse;void main()\n{_ret_0=texture2D(diffuse,tz_TexCoord[0].xy);gl_FragColor=_ret_0;}"
                    },
                    "vp": {
                        "type": "vertex",
                        "code": "#ifdef GL_ES\nprecision mediump float;precision mediump int;\n#endif\nvarying vec4 tz_TexCoord[1];attribute vec4 ATTR0;attribute vec4 ATTR8;\nvec4 _OUTpos1;vec2 _OUTuv1;uniform vec4 worldViewProjection[4];void main()\n{_OUTpos1=ATTR0.xxxx*worldViewProjection[0]+ATTR0.yyyy*worldViewProjection[1]+ATTR0.zzzz*worldViewProjection[2]+worldViewProjection[3];_OUTuv1=ATTR8.xy;tz_TexCoord[0].xy=ATTR8.xy;gl_Position=_OUTpos1;}"
                    }
                }
            };

            defaultShader = gd.createShader(shaderParams);
            if (!defaultShader) {
                errorCallback("Default shader not created.");
            }
        }

        var shaders = {};
        var loadingShader = {};
        var loadedObservers = {};
        var numLoadingShaders = 0;
        var pathRemapping = null;
        var pathPrefix = "";
        var doPreprocess = false;
        var resizeParameters = {};

        shaders[defaultShaderName] = defaultShader;

        function preprocessShader(shader) {
            var parameters = shader.parameters;
            var techniques = shader.techniques;
            var programs = shader.programs;
            var p, resize, programsToUpdate, t;
            var passes, numPasses, a, pass, passPrograms;
            var length, n, reg, rep, u, program;
            for (p in parameters) {
                if (parameters.hasOwnProperty(p)) {
                    resize = resizeParameters[p];
                    if (resize !== undefined) {
                        parameters[p].rows = resize;

                        programsToUpdate = {};
                        for (t in techniques) {
                            if (techniques.hasOwnProperty(t)) {
                                passes = techniques[t];
                                numPasses = passes.length;
                                for (a = 0; a < numPasses; a += 1) {
                                    pass = passes[a];
                                    if (pass.parameters.indexOf(p) !== -1) {
                                        passPrograms = pass.programs;
                                        length = passPrograms.length;
                                        for (n = 0; n < length; n += 1) {
                                            programsToUpdate[passPrograms[n]] = true;
                                        }
                                    }
                                }
                            }
                        }

                        reg = new RegExp("uniform\\s+(\\w+)\\s+" + p + "\\s*\\[[^\\]]+\\]", "mg");
                        rep = "uniform $1 " + p + "[" + resize + "]";
                        for (u in programsToUpdate) {
                            if (programsToUpdate.hasOwnProperty(u)) {
                                program = programs[u];
                                program.code = program.code.replace(reg, rep);
                            }
                        }
                    }
                }
            }
        }

        /**
        Creates shader from an cgfx file
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name load
        
        @param {string} path Path to the cgfx file
        
        @return {Shader} object, returns the default shader if the file at given path is not yet loaded
        */
        var loadShader = function loadShaderFn(path, onShaderLoaded) {
            if (path === undefined) {
                errorCallback("Invalid texture path passed to ShaderManager.Load");
            }
            var shader = shaders[path];
            if (!shader) {
                if (!loadingShader[path]) {
                    loadingShader[path] = true;
                    numLoadingShaders += 1;

                    var observer = Observer.create();
                    loadedObservers[path] = observer;
                    if (onShaderLoaded) {
                        observer.subscribe(onShaderLoaded);
                    }

                    var shaderLoaded = function shaderLoadedFn(shaderText/*, status, callContext */ ) {
                        if (shaderText) {
                            var shaderParameters = JSON.parse(shaderText);
                            if (doPreprocess) {
                                preprocessShader(shaderParameters);
                            }
                            var s = gd.createShader(shaderParameters);
                            if (s) {
                                shaders[path] = s;
                            } else {
                                delete shaders[path];
                            }

                            observer.notify(s);
                            delete loadedObservers[path];
                        } else {
                            if (log) {
                                log.innerHTML += "ShaderManager.load:&nbsp;'" + path + "' failed to load<br>";
                            }
                            delete shaders[path];
                        }
                        delete loadingShader[path];

                        numLoadingShaders -= 1;
                    };

                    rh.request({
                        src: ((pathRemapping && pathRemapping[path]) || (pathPrefix + path)),
                        onload: shaderLoaded
                    });
                } else if (onShaderLoaded) {
                    loadedObservers[path].subscribe(onShaderLoaded);
                }

                return defaultShader;
            } else if (onShaderLoaded) {
                // the callback should always be called asynchronously
                TurbulenzEngine.setTimeout(function shaderAlreadyLoadedFn() {
                    onShaderLoaded(shader);
                }, 0);
            }

            return shader;
        };

        /**
        Alias one shader to another name
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name map
        
        @param {string} dst Name of the alias
        @param {string} src Name of the shader to be aliased
        */
        var mapShader = function mapShaderFn(dst, src) {
            shaders[dst] = shaders[src];
        };

        /**
        Get shader created from a given shader file or with the given name
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name get
        
        @param {string} path Path or name of the shader
        
        @return {Shader} object, returns the default shader if the shader is not yet loaded or the shader file didn't exist
        */
        var getShader = function getShaderFn(path) {
            var shader = shaders[path];
            if (!shader) {
                return defaultShader;
            }
            return shader;
        };

        /**
        Removes a shader from the manager
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name remove
        
        @param {string} path Path or name of the shader
        */
        var removeShader = function removeShaderFn(path) {
            if (typeof shaders[path] !== 'undefined') {
                delete shaders[path];
            }
        };

        /**
        Reloads a shader
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name reload
        
        @param {string} path Path or name of the shader
        */
        var reloadShader = function reloadShaderFn(path, callback) {
            removeShader(path);
            loadShader(path, callback);
        };

        var sm = new ShaderManager();

        if (log) {
            sm.load = function loadShaderLogFn(path, callback) {
                log.innerHTML += "ShaderManager.load:&nbsp;'" + path + "'<br>";
                return loadShader(path, callback);
            };

            sm.map = function mapShaderLogFn(dst, src) {
                log.innerHTML += "ShaderManager.map:&nbsp;'" + src + "' -> '" + dst + "'<br>";
                mapShader(dst, src);
            };

            sm.get = function getShaderLogFn(path) {
                log.innerHTML += "ShaderManager.get:&nbsp;'" + path + "'<br>";
                return getShader(path);
            };

            sm.remove = function removeShaderLogFn(path) {
                log.innerHTML += "ShaderManager.remove:&nbsp;'" + path + "'<br>";
                removeShader(path);
            };

            sm.reload = function reloadShaderLogFn(path, callback) {
                log.innerHTML += "ShaderManager. reload:&nbsp;'" + path + "'<br>";
                reloadShader(path, callback);
            };
        } else {
            sm.load = loadShader;
            sm.map = mapShader;
            sm.get = getShader;
            sm.remove = removeShader;
            sm.reload = reloadShader;
        }

        /**
        Reloads all shaders
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name reloadAll
        */
        sm.reloadAll = function reloadAllShadersFn() {
            for (var t in shaders) {
                if (shaders.hasOwnProperty(t) && t !== defaultShaderName) {
                    reloadShader(t);
                }
            }
        };

        /**
        Get object containing all loaded shaders
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name getAll
        
        @return {object}
        */
        sm.getAll = function getAllShadersFn() {
            return shaders;
        };

        /**
        Get number of shaders pending
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name getNumLoadingShaders
        
        @return {number}
        */
        sm.getNumPendingShaders = function getNumPendingShadersFn() {
            return numLoadingShaders;
        };

        /**
        Check if a shader is not pending
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name isShaderLoaded
        
        @param {string} path Path or name of the shader
        
        @return {boolean}
        */
        sm.isShaderLoaded = function isShaderLoadedFn(path) {
            return !loadingShader[path];
        };

        /**
        Check if a shader is missing
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name isShaderMissing
        
        @param {string} path Path or name of the shader
        
        @return {boolean}
        */
        sm.isShaderMissing = function isShaderMissingFn(path) {
            return !shaders[path];
        };

        /**
        Set path remapping dictionary
        
        @memberOf ShaderManager.prototype
        @public
        @function
        @name setPathRemapping
        
        @param {string} prm Path remapping dictionary
        @param {string} assetUrl Asset prefix for all assets loaded
        */
        sm.setPathRemapping = function setPathRemappingFn(prm, assetUrl) {
            pathRemapping = prm;
            pathPrefix = assetUrl;
        };

        sm.setAutomaticParameterResize = function setAutomaticParameterResizeFn(name, size) {
            doPreprocess = true;
            resizeParameters[name] = size;
        };

        sm.destroy = function shaderManagerDestroyFn() {
            if (shaders) {
                var p;
                for (p in shaders) {
                    if (shaders.hasOwnProperty(p)) {
                        var shader = shaders[p];
                        if (shader) {
                            shader.destroy();
                        }
                    }
                }
                shaders = null;
            }

            defaultShader = null;
            loadingShader = null;
            loadedObservers = null;
            numLoadingShaders = 0;
            pathRemapping = null;
            pathPrefix = null;
            rh = null;
            gd = null;
        };

        return sm;
    };
    ShaderManager.version = 1;
    return ShaderManager;
})();

// Copyright (c) 2009-2012 Turbulenz Limited
/*global Reference: false*/
/*global Observer: false*/
/*global TurbulenzEngine: false*/

var TextureInstance = (function () {
    function TextureInstance() {
    }
    //
    // setTexture
    //
    TextureInstance.prototype.setTexture = function (texture) {
        this.texture = texture;
        if (this.textureChangedObserver) {
            this.textureChangedObserver.notify(this);
        }
    };

    //
    // getTexture
    //
    TextureInstance.prototype.getTexture = function () {
        return this.texture;
    };

    //
    // subscribeTextureChanged
    //
    TextureInstance.prototype.subscribeTextureChanged = function (observerFunction) {
        if (!this.textureChangedObserver) {
            this.textureChangedObserver = Observer.create();
        }
        this.textureChangedObserver.subscribe(observerFunction);
    };

    //
    // usubscribeTextureChanged
    //
    TextureInstance.prototype.unsubscribeTextureChanged = function (observerFunction) {
        this.textureChangedObserver.unsubscribe(observerFunction);
    };

    //
    // destroy
    //
    TextureInstance.prototype.destroy = function () {
        if (this.texture.name !== "default") {
            this.texture.destroy();
        }
        delete this.texture;
        delete this.textureChangedObserver;
    };

    TextureInstance.create = //
    // TextureInstance.create
    //
    function (name, texture) {
        var textureInstance = new TextureInstance();
        textureInstance.name = name;
        textureInstance.texture = texture;
        textureInstance.reference = Reference.create(textureInstance);

        return textureInstance;
    };
    TextureInstance.version = 1;
    return TextureInstance;
})();

/**
@class  Texture manager
@private

@since TurbulenzEngine 0.1.0
*/
var TextureManager = (function () {
    function TextureManager() {
    }
    /**
    Adds external texture
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name add
    
    @param {string} name Name of the texture
    @param {Texture} texture Texture
    */
    TextureManager.prototype.add = function (name, texture, internal) {
        var textureInstance = this.textureInstances[name];
        if (!textureInstance) {
            this.textureInstances[name] = TextureInstance.create(name, texture);
            this.textureInstances[name].reference.subscribeDestroyed(this.onTextureInstanceDestroyed);
        } else {
            textureInstance.setTexture(texture);
        }

        if (internal) {
            this.internalTexture[name] = true;
            this.textureInstances[name].reference.add();
        }
    };

    /**
    Get texture created from a given file or with the given name
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name get
    
    @param {string} path Path or name of the texture
    
    @return {Texture} object, returns the default texture if the texture is not yet loaded or the file didn't exist
    */
    TextureManager.prototype.get = function (path) {
        var instance = this.textureInstances[path];
        if (!instance) {
            return this.defaultTexture;
        }
        return instance.getTexture();
    };

    //
    // getInstanceFn
    //
    TextureManager.prototype.getInstance = function (path) {
        return this.textureInstances[path];
    };

    /**
    Creates texture from an image file
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name load
    
    @param {string} path Path to the image file
    @param {boolean} nomipmaps True to disable mipmaps
    @param {function()} onTextureLoaded function to call once the texture is loaded
    
    @return {Texture} object, returns the default Texture if the file at given path is not yet loaded
    */
    TextureManager.prototype.load = function (path, nomipmaps, onTextureLoaded) {
        var that = this;

        if (path === undefined) {
            this.errorCallback("Invalid texture path passed to TextureManager.Load");
        }
        var textureInstance = this.textureInstances[path];
        if (!textureInstance || (textureInstance.texture === this.defaultTexture && path !== "default")) {
            if (!textureInstance) {
                this.add(path, this.defaultTexture, false);
            }

            if (!(path in this.loadingTexture)) {
                if (0 === this.numLoadingArchives) {
                    this.loadingTexture[path] = true;
                    this.numLoadingTextures += 1;

                    var mipmaps = true;
                    if (nomipmaps) {
                        mipmaps = false;
                    }

                    var loadedObserver = Observer.create();
                    this.loadedTextureObservers[path] = loadedObserver;
                    if (onTextureLoaded) {
                        loadedObserver.subscribe(onTextureLoaded);
                    }

                    var textureLoaded = function textureLoadedFn(texture, status) {
                        if (status === 200 && texture) {
                            that.add(path, texture, false);
                        }

                        loadedObserver.notify(texture);
                        delete that.loadedTextureObservers[path];

                        //Missing textures are left with the previous, usually default, texture.
                        delete that.loadingTexture[path];
                        that.numLoadingTextures -= 1;
                    };

                    var textureRequest = function textureRequestFn(url, onload/*, callContext */ ) {
                        var texture = that.graphicsDevice.createTexture({
                            src: url,
                            mipmaps: mipmaps,
                            onload: onload
                        });
                        if (!texture) {
                            that.errorCallback("Texture '" + url + "' not created.");
                        }
                    };

                    this.requestHandler.request({
                        src: ((this.pathRemapping && this.pathRemapping[path]) || (this.pathPrefix + path)),
                        requestFn: textureRequest,
                        onload: textureLoaded
                    });
                } else {
                    this.delayedTextures[path] = {
                        nomipmaps: nomipmaps,
                        onload: onTextureLoaded
                    };

                    return this.get(path);
                }
            } else if (onTextureLoaded) {
                this.loadedTextureObservers[path].subscribe(onTextureLoaded);
            }

            return this.get(path);
        } else {
            var texture = this.get(path);
            if (onTextureLoaded) {
                // the callback should always be called asynchronously
                TurbulenzEngine.setTimeout(function textureAlreadyLoadedFn() {
                    onTextureLoaded(texture);
                }, 0);
            }
            return texture;
        }
    };

    /**
    Alias one texture to another name
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name map
    
    @param {string} dst Name of the alias
    @param {string} src Name of the texture to be aliased
    */
    TextureManager.prototype.map = function (dst, src) {
        if (!this.textureInstances[dst]) {
            this.textureInstances[dst] = TextureInstance.create(dst, this.textureInstances[src].getTexture());
            this.textureInstances[dst].reference.subscribeDestroyed(this.onTextureInstanceDestroyed);
        } else {
            this.textureInstances[dst].setTexture(this.textureInstances[src].getTexture());
        }
        this.internalTexture[dst] = true;
    };

    /**
    Removes a texture from the manager
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name remove
    
    @param {string} path Path or name of the texture
    */
    TextureManager.prototype.remove = function (path) {
        if (!this.internalTexture[path]) {
            if (path in this.textureInstances) {
                this.textureInstances[path].reference.unsubscribeDestroyed(this.onTextureInstanceDestroyed);
                delete this.textureInstances[path];
            }
        }
    };

    /**
    Loads a textures archive
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name loadArchive
    
    @param {string} path Path to the archive file
    @param {boolean} nomipmaps True to disable mipmaps
    */
    TextureManager.prototype.loadArchive = function (path, nomipmaps, onTextureLoaded, onArchiveLoaded) {
        var that = this;
        var archive = this.archivesLoaded[path];
        if (!archive) {
            if (!(path in this.loadingArchives)) {
                var mipmaps = true;
                if (nomipmaps) {
                    mipmaps = false;
                }
                this.loadingArchives[path] = { textures: {} };
                this.numLoadingArchives += 1;

                var observer = Observer.create();
                this.loadedArchiveObservers[path] = observer;
                if (onArchiveLoaded) {
                    observer.subscribe(onArchiveLoaded);
                }

                var textureArchiveLoaded = function textureArchiveLoadedFn(success, status) {
                    var loadedArchive;
                    if (status === 200 && success) {
                        loadedArchive = { textures: that.loadingArchives[path].textures };
                        that.archivesLoaded[path] = loadedArchive;
                    }

                    observer.notify(loadedArchive);
                    delete that.loadedArchiveObservers[path];

                    delete that.loadingArchives[path];
                    that.numLoadingArchives -= 1;
                    if (0 === that.numLoadingArchives) {
                        var name;
                        for (name in that.delayedTextures) {
                            if (that.delayedTextures.hasOwnProperty(name)) {
                                var delayedTexture = that.delayedTextures[name];
                                that.load(name, delayedTexture.nomipmaps, delayedTexture.onload);
                            }
                        }
                        that.delayedTextures = {};
                    }
                };

                var requestTextureArchive = function requestTextureArchiveFn(url, onload) {
                    var ontextureload = function ontextureloadFn(texture) {
                        var name = texture.name;
                        if (!(name in that.textureInstances) || that.textureInstances[name].texture === that.defaultTexture) {
                            that.add(name, texture, false);
                            that.loadingArchives[path].textures[name] = texture;
                        }

                        if (onTextureLoaded) {
                            onTextureLoaded(texture);
                        }

                        delete that.delayedTextures[name];
                        if (path in that.loadingTexture) {
                            delete that.loadingTexture[path];
                            that.numLoadingTextures -= 1;
                        }
                    };

                    if (!that.graphicsDevice.loadTexturesArchive({
                        src: url,
                        mipmaps: mipmaps,
                        ontextureload: ontextureload,
                        onload: onload
                    })) {
                        that.errorCallback("Archive '" + path + "' not loaded.");
                    }
                };

                that.requestHandler.request({
                    src: ((that.pathRemapping && that.pathRemapping[path]) || (that.pathPrefix + path)),
                    requestFn: requestTextureArchive,
                    onload: textureArchiveLoaded
                });
            } else if (onTextureLoaded) {
                this.loadedArchiveObservers[path].subscribe(function textureArchiveLoadedFn() {
                    var archive = that.archivesLoaded[path];
                    var texturesInArchive = archive.textures;
                    var t;
                    for (t in texturesInArchive) {
                        if (texturesInArchive.hasOwnProperty(t)) {
                            // the texture has already been loaded so we call onload manaually
                            onTextureLoaded(texturesInArchive[t]);
                        }
                    }
                    if (onArchiveLoaded) {
                        onArchiveLoaded(archive);
                    }
                });
            }
        } else {
            if (onTextureLoaded) {
                var texturesInArchive = archive.textures;
                var numTexturesLoading = 0;

                var textureAlreadyLoadedWrapper = function textureAlreadyLoadedWrapper(texture) {
                    return function textureAlreadyLoadedFn() {
                        onTextureLoaded(texture);
                        numTexturesLoading -= 1;
                        if (numTexturesLoading === 0 && onArchiveLoaded) {
                            onArchiveLoaded(archive);
                        }
                    };
                };

                var t;
                for (t in texturesInArchive) {
                    if (texturesInArchive.hasOwnProperty(t)) {
                        numTexturesLoading += 1;

                        // the callback should always be called asynchronously
                        TurbulenzEngine.setTimeout(textureAlreadyLoadedWrapper(texturesInArchive[t]), 0);
                    }
                }
            }
        }
    };

    /**
    Check if an archive is not pending
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name isArchiveLoaded
    
    @param {string} path Path or name of the archive
    
    @return {boolean}
    */
    TextureManager.prototype.isArchiveLoaded = function (path) {
        return path in this.archivesLoaded;
    };

    /**
    Removes a textures archive and all the textures it references.
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name removeArchive
    
    @param {string} path Path of the archive file
    */
    TextureManager.prototype.removeArchive = function (path) {
        if (path in this.archivesLoaded) {
            var archiveTextures = this.archivesLoaded[path].textures;
            var texture;
            for (texture in archiveTextures) {
                if (archiveTextures.hasOwnProperty(texture)) {
                    this.remove(texture);
                }
            }
            delete this.archivesLoaded[path];
        }
    };

    /**
    Get object containing all loaded textures
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name getAll
    
    @return {object}
    */
    TextureManager.prototype.getAll = function () {
        return this.textureInstances;
    };

    /**
    Get number of textures pending
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name getNumLoadingTextures
    
    @return {number}
    */
    TextureManager.prototype.getNumPendingTextures = function () {
        return (this.numLoadingTextures + this.numLoadingArchives);
    };

    /**
    Check if a texture is not pending
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name isTextureLoaded
    
    @param {string} path Path or name of the texture
    
    @return {boolean}
    */
    TextureManager.prototype.isTextureLoaded = function (path) {
        return (!(path in this.loadingTexture) && !(path in this.delayedTextures));
    };

    /**
    Check if a texture is missing
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name isTextureMissing
    
    @param {string} path Path or name of the texture
    
    @return {boolean}
    */
    TextureManager.prototype.isTextureMissing = function (path) {
        return !(path in this.textureInstances);
    };

    /**
    Set path remapping dictionary
    
    @memberOf TextureManager.prototype
    @public
    @function
    @name setPathRemapping
    
    @param {string} prm Path remapping dictionary
    @param {string} assetUrl Asset prefix for all assets loaded
    */
    TextureManager.prototype.setPathRemapping = function (prm, assetUrl) {
        this.pathRemapping = prm;
        this.pathPrefix = assetUrl;
    };

    TextureManager.prototype.addProceduralTexture = function (params) {
        var name = params.name;
        var procTexture = this.graphicsDevice.createTexture(params);
        if (!procTexture) {
            this.errorCallback("Failed to create '" + name + "' texture.");
        } else {
            this.add(name, procTexture, true);
        }
    };

    TextureManager.prototype.destroy = function () {
        if (this.textureInstances) {
            var p;
            for (p in this.textureInstances) {
                if (this.textureInstances.hasOwnProperty(p)) {
                    var textureInstance = this.textureInstances[p];
                    if (textureInstance) {
                        textureInstance.destroy();
                    }
                }
            }
            this.textureInstances = null;
        }

        if (this.defaultTexture) {
            this.defaultTexture.destroy();
            this.defaultTexture = null;
        }

        this.loadingTexture = null;
        this.loadedTextureObservers = null;
        this.delayedTextures = null;
        this.numLoadingTextures = 0;
        this.archivesLoaded = null;
        this.loadingArchives = null;
        this.loadedArchiveObservers = null;
        this.numLoadingArchives = 0;
        this.internalTexture = null;
        this.pathRemapping = null;
        this.pathPrefix = null;
        this.requestHandler = null;
        this.graphicsDevice = null;
    };

    TextureManager.create = /**
    @constructs Constructs a TextureManager object.
    
    @param {GraphicsDevice} graphicsDevice Graphics device
    @param {Texture} dt Default texture
    @param {Element} log Logging element
    
    @return {TextureManager} object, null if failed
    */
    function (graphicsDevice, requestHandler, dt, errorCallback, log) {
        var textureManager = new TextureManager();

        if (!errorCallback) {
            errorCallback = function (/* e */ ) {
            };
        }

        var defaultTextureName = "default";

        var defaultTexture;
        if (dt) {
            defaultTexture = dt;
        } else {
            defaultTexture = graphicsDevice.createTexture({
                name: defaultTextureName,
                width: 2,
                height: 2,
                depth: 1,
                format: 'R8G8B8A8',
                cubemap: false,
                mipmaps: true,
                dynamic: false,
                data: [
                    255,
                    20,
                    147,
                    255,
                    255,
                    0,
                    0,
                    255,
                    255,
                    255,
                    255,
                    255,
                    255,
                    20,
                    147,
                    255
                ]
            });
            if (!defaultTexture) {
                errorCallback("Default texture not created.");
            }
        }

        textureManager.textureInstances = {};
        textureManager.loadingTexture = {};
        textureManager.loadedTextureObservers = {};
        textureManager.delayedTextures = {};
        textureManager.numLoadingTextures = 0;
        textureManager.archivesLoaded = {};
        textureManager.loadingArchives = {};
        textureManager.loadedArchiveObservers = {};
        textureManager.numLoadingArchives = 0;
        textureManager.internalTexture = {};
        textureManager.pathRemapping = null;
        textureManager.pathPrefix = "";

        textureManager.graphicsDevice = graphicsDevice;
        textureManager.requestHandler = requestHandler;
        textureManager.defaultTexture = defaultTexture;
        textureManager.errorCallback = errorCallback;

        //
        // onTextureInstanceDestroyed callback
        //
        var onTextureInstanceDestroyed = function onTextureInstanceDestroyedFn(textureInstance) {
            textureInstance.reference.unsubscribeDestroyed(onTextureInstanceDestroyed);
            delete textureManager.textureInstances[textureInstance.name];
        };
        textureManager.onTextureInstanceDestroyed = onTextureInstanceDestroyed;

        if (log) {
            textureManager.add = function addTextureLogFn(name, tex) {
                log.innerHTML += "TextureManager.add:&nbsp;'" + name + "'";
                return TextureManager.prototype.add.call(textureManager, name, tex);
            };

            textureManager.load = function loadTextureLogFn(path, nomipmaps) {
                log.innerHTML += "TextureManager.load:&nbsp;'" + path + "'";
                return TextureManager.prototype.load.call(textureManager, path, nomipmaps);
            };

            textureManager.loadArchive = function loadArchiveLogFn(path, nomipmaps) {
                log.innerHTML += "TextureManager.loadArchive:&nbsp;'" + path + "'";
                return TextureManager.prototype.loadArchive.call(textureManager, path, nomipmaps);
            };

            textureManager.isArchiveLoaded = function isArchiveLoadedLogFn(path) {
                log.innerHTML += "TextureManager.isArchiveLoaded:&nbsp;'" + path + "'";
                return TextureManager.prototype.isArchiveLoaded.call(textureManager, path);
            };

            textureManager.removeArchive = function removeArchiveLogFn(path) {
                log.innerHTML += "TextureManager.removeArchive:&nbsp;'" + path + "'";
                return TextureManager.prototype.removeArchive.call(textureManager, path);
            };

            textureManager.map = function mapTextureLogFn(dst, src) {
                log.innerHTML += "TextureManager.map:&nbsp;'" + src + "' -> '" + dst + "'";
                TextureManager.prototype.map.call(textureManager, dst, src);
            };

            textureManager.get = function getTextureLogFn(path) {
                log.innerHTML += "TextureManager.get:&nbsp;'" + path + "'";
                return TextureManager.prototype.get.call(textureManager, path);
            };

            textureManager.getInstance = function getTextureInstanceLogFn(path) {
                log.innerHTML += "TextureManager.getInstance:&nbsp;'" + path + "'";
                return TextureManager.prototype.getInstance.call(textureManager, path);
            };

            textureManager.remove = function removeTextureLogFn(path) {
                log.innerHTML += "TextureManager.remove:&nbsp;'" + path + "'";
                TextureManager.prototype.remove.call(textureManager, path);
            };
        }

        // Add procedural textures
        textureManager.add(defaultTextureName, defaultTexture, true);

        textureManager.addProceduralTexture({
            name: "white",
            width: 2,
            height: 2,
            depth: 1,
            format: 'R8G8B8A8',
            cubemap: false,
            mipmaps: true,
            dynamic: false,
            data: [
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255,
                255
            ]
        });

        textureManager.addProceduralTexture({
            name: "black",
            width: 2,
            height: 2,
            depth: 1,
            format: 'R8G8B8A8',
            cubemap: false,
            mipmaps: true,
            dynamic: false,
            data: [
                0,
                0,
                0,
                255,
                0,
                0,
                0,
                255,
                0,
                0,
                0,
                255,
                0,
                0,
                0,
                255
            ]
        });

        textureManager.addProceduralTexture({
            name: "flat",
            width: 2,
            height: 2,
            depth: 1,
            format: 'R8G8B8A8',
            cubemap: false,
            mipmaps: true,
            dynamic: false,
            data: [
                128,
                128,
                255,
                255,
                128,
                128,
                255,
                255,
                128,
                128,
                255,
                255,
                128,
                128,
                255,
                255
            ]
        });

        var abs = Math.abs;
        var x, y;
        var quadraticData = [];
        for (y = 0; y < 4; y += 1) {
            for (x = 0; x < 32; x += 1) {
                var s = ((x + 0.5) * (2.0 / 32.0) - 1.0);
                s = abs(s) - (1.0 / 32.0);
                var value = (1.0 - (s * 2.0) + (s * s));
                if (value <= 0) {
                    quadraticData.push(0);
                } else if (value >= 1) {
                    quadraticData.push(255);
                } else {
                    quadraticData.push(value * 255);
                }
            }
        }
        textureManager.addProceduralTexture({
            name: "quadratic",
            width: 32,
            height: 4,
            depth: 1,
            format: 'L8',
            cubemap: false,
            mipmaps: true,
            dynamic: false,
            data: quadraticData
        });
        quadraticData = null;

        var nofalloffData = [];
        for (y = 0; y < 4; y += 1) {
            nofalloffData.push(0);
            for (x = 1; x < 31; x += 1) {
                nofalloffData.push(255);
            }
            nofalloffData.push(0);
        }
        textureManager.addProceduralTexture({
            name: "nofalloff",
            width: 32,
            height: 4,
            depth: 1,
            format: 'L8',
            cubemap: false,
            mipmaps: true,
            dynamic: false,
            data: nofalloffData
        });
        nofalloffData = null;

        return textureManager;
    };
    TextureManager.version = 1;
    return TextureManager;
})();

// Copyright (c) 2010-2014 Turbulenz Limited
/*exported renderingCommonSortKeyFn*/
/*exported renderingCommonCreateRendererInfoFn*/
/*exported renderingCommonAddDrawParameterFastestFn*/
//
// renderingCommonGetTechniqueIndexFn
//
var renderingCommonGetTechniqueIndexFn = function renderingCommonGetTechniqueIndexFnFn(techniqueName) {
    var dataStore = renderingCommonGetTechniqueIndexFn;
    var techniqueIndex = dataStore.techniquesIndexMap[techniqueName];
    if (techniqueIndex === undefined) {
        techniqueIndex = dataStore.numTechniques;
        dataStore.techniquesIndexMap[techniqueName] = techniqueIndex;
        dataStore.numTechniques += 1;
    }
    return techniqueIndex;
};

renderingCommonGetTechniqueIndexFn.techniquesIndexMap = {};
renderingCommonGetTechniqueIndexFn.numTechniques = 0;

//
// renderingCommonSortKeyFn
//
function renderingCommonSortKeyFn(techniqueIndex, materialIndex, nodeIndex) {
    var sortKey = ((techniqueIndex * 0x10000) + (materialIndex % 0x10000));
    if (nodeIndex) {
        sortKey += (1.0 / (1.0 + nodeIndex));
    }
    return sortKey;
}

//
// renderingCommonCreateRendererInfoFn
//
function renderingCommonCreateRendererInfoFn(renderable) {
    var rendererInfo = {
        far: renderable.sharedMaterial.meta.far
    };
    renderable.rendererInfo = rendererInfo;

    var effect = renderable.sharedMaterial.effect;
    if (effect.prepare) {
        effect.prepare(renderable);
    }

    return rendererInfo;
}

//
// renderingCommonAddDrawParameterFastestFn
//
var renderingCommonAddDrawParameterFastestFn = function renderingCommonAddDrawParameterFastestFnFn(drawParameters) {
    var array = this.array;
    array[array.length] = drawParameters;
};

// Copyright (c) 2009-2014 Turbulenz Limited
;

;

var DefaultRendering = (function () {
    function DefaultRendering() {
    }
    /* tslint:disable:no-empty */
    DefaultRendering.prototype.updateShader = function (/* sm */ ) {
    };

    /* tslint:enable:no-empty */
    DefaultRendering.prototype.sortRenderablesAndLights = function (camera, scene) {
        var opaque = DefaultRendering.passIndex.opaque;
        var decal = DefaultRendering.passIndex.decal;
        var transparent = DefaultRendering.passIndex.transparent;

        var passes = this.passes;
        var opaquePass = passes[opaque];
        var decalPass = passes[decal];
        var transparentPass = passes[transparent];

        var numOpaque = 0;
        var numDecal = 0;
        var numTransparent = 0;

        var drawParametersArray;
        var numDrawParameters;
        var drawParameters;
        var drawParametersIndex;

        var visibleRenderables = scene.getCurrentVisibleRenderables();
        var numVisibleRenderables = visibleRenderables.length;
        if (numVisibleRenderables > 0) {
            var renderable, passIndex;
            var n = 0;
            do {
                renderable = visibleRenderables[n];

                if (!renderable.renderUpdate) {
                    var effect = renderable.sharedMaterial.effect;
                    if (effect.prepare) {
                        effect.prepare(renderable);
                    }
                }

                renderable.renderUpdate(camera);

                drawParametersArray = renderable.drawParameters;
                numDrawParameters = drawParametersArray.length;
                for (drawParametersIndex = 0; drawParametersIndex < numDrawParameters; drawParametersIndex += 1) {
                    drawParameters = drawParametersArray[drawParametersIndex];
                    passIndex = drawParameters.userData.passIndex;
                    if (passIndex === opaque) {
                        opaquePass[numOpaque] = drawParameters;
                        numOpaque += 1;
                    } else if (passIndex === transparent) {
                        if (renderable.sharedMaterial.meta.far) {
                            drawParameters.sortKey = 1.e38;
                        } else {
                            drawParameters.sortKey = renderable.distance;
                        }

                        transparentPass[numTransparent] = drawParameters;
                        numTransparent += 1;
                    } else if (passIndex === decal) {
                        decalPass[numDecal] = drawParameters;
                        numDecal += 1;
                    }
                }

                // this renderer does not care about lights
                n += 1;
            } while(n < numVisibleRenderables);
        }

        opaquePass.length = numOpaque;
        decalPass.length = numDecal;
        transparentPass.length = numTransparent;
    };

    DefaultRendering.prototype.update = function (gd, camera, scene, currentTime) {
        scene.updateVisibleNodes(camera);

        this.sortRenderablesAndLights(camera, scene);

        var matrix = camera.matrix;
        if (matrix[9] !== this.eyePosition[0] || matrix[10] !== this.eyePosition[1] || matrix[11] !== this.eyePosition[2]) {
            this.eyePositionUpdated = true;
            this.eyePosition[0] = matrix[9];
            this.eyePosition[1] = matrix[10];
            this.eyePosition[2] = matrix[11];
        } else {
            this.eyePositionUpdated = false;
        }

        /* tslint:disable:no-string-literal */
        this.globalTechniqueParameters['time'] = currentTime;

        /* tslint:enable:no-string-literal */
        this.camera = camera;
        this.scene = scene;
    };

    DefaultRendering.prototype.updateBuffers = function (gd, deviceWidth, deviceHeight) {
        return true;
    };

    DefaultRendering.prototype.draw = function (gd, clearColor, drawDecalsFn, drawTransparentFn, drawDebugFn) {
        gd.clear(clearColor, 1.0, 0);

        if (this.wireframe) {
            this.scene.drawWireframe(gd, this.sm, this.camera, this.wireframeInfo);

            if (drawDecalsFn) {
                drawDecalsFn();
            }

            if (drawTransparentFn) {
                drawTransparentFn();
            }
        } else {
            var globalTechniqueParametersArray = this.globalTechniqueParametersArray;
            var passes = this.passes;

            gd.drawArray(passes[DefaultRendering.passIndex.opaque], globalTechniqueParametersArray, -1);

            gd.drawArray(passes[DefaultRendering.passIndex.decal], globalTechniqueParametersArray, -1);

            if (drawDecalsFn) {
                drawDecalsFn();
            }

            gd.drawArray(passes[DefaultRendering.passIndex.transparent], globalTechniqueParametersArray, 1);

            if (drawTransparentFn) {
                drawTransparentFn();
            }
        }

        if (drawDebugFn) {
            drawDebugFn();
        }

        this.lightPositionUpdated = false;
    };

    DefaultRendering.prototype.setGlobalLightPosition = function (pos) {
        this.lightPositionUpdated = true;
        this.lightPosition[0] = pos[0];
        this.lightPosition[1] = pos[1];
        this.lightPosition[2] = pos[2];
    };

    /* tslint:disable:no-string-literal */
    DefaultRendering.prototype.setGlobalLightColor = function (color) {
        this.globalTechniqueParameters['lightColor'] = color;
    };

    DefaultRendering.prototype.setAmbientColor = function (color) {
        this.globalTechniqueParameters['ambientColor'] = color;
    };

    DefaultRendering.prototype.setDefaultTexture = function (tex) {
        this.globalTechniqueParameters['diffuse'] = tex;
    };

    /* tslint:enable:no-string-literal */
    DefaultRendering.prototype.setWireframe = function (wireframeEnabled, wireframeInfo) {
        this.wireframeInfo = wireframeInfo;
        this.wireframe = wireframeEnabled;
    };

    DefaultRendering.prototype.getDefaultSkinBufferSize = function () {
        return this.defaultSkinBufferSize;
    };

    DefaultRendering.prototype.destroy = function () {
        delete this.globalTechniqueParametersArray;
        delete this.globalTechniqueParameters;
        delete this.lightPosition;
        delete this.eyePosition;
        delete this.passes;
    };

    DefaultRendering.defaultPrepareFn = //
    // defaultPrepareFn
    //
    function (geometryInstance) {
        var drawParameters = TurbulenzEngine.getGraphicsDevice().createDrawParameters();
        drawParameters.userData = {};
        geometryInstance.drawParameters = [drawParameters];
        geometryInstance.prepareDrawParameters(drawParameters);

        var sharedMaterial = geometryInstance.sharedMaterial;
        var techniqueParameters = geometryInstance.techniqueParameters;

        if (!sharedMaterial.techniqueParameters.materialColor && !techniqueParameters.materialColor) {
            sharedMaterial.techniqueParameters.materialColor = DefaultRendering.v4One;
        }

        if (!sharedMaterial.techniqueParameters.uvTransform && !techniqueParameters.uvTransform) {
            sharedMaterial.techniqueParameters.uvTransform = DefaultRendering.identityUVTransform;
        }

        // NOTE: the way this functions is called, 'this' is an
        // EffectPrepareObject.
        drawParameters.technique = (this).technique;

        drawParameters.setTechniqueParameters(0, sharedMaterial.techniqueParameters);
        drawParameters.setTechniqueParameters(1, techniqueParameters);

        if (sharedMaterial.meta.decal) {
            drawParameters.userData.passIndex = DefaultRendering.passIndex.decal;
        } else if (sharedMaterial.meta.transparent) {
            drawParameters.userData.passIndex = DefaultRendering.passIndex.transparent;
        } else {
            drawParameters.userData.passIndex = DefaultRendering.passIndex.opaque;
        }

        var node = geometryInstance.node;
        if (!node.rendererInfo) {
            var md = TurbulenzEngine.getMathDevice();
            node.rendererInfo = {
                id: DefaultRendering.nextRenderinfoID,
                frameVisible: -1,
                worldUpdate: -1,
                worldViewProjection: md.m44BuildIdentity(),
                worldInverse: md.m43BuildIdentity(),
                eyePosition: md.v3BuildZero(),
                lightPosition: md.v3BuildZero()
            };
            DefaultRendering.nextRenderinfoID += 1;
        }

        // do this once instead of for every update
        var rendererInfo = node.rendererInfo;
        techniqueParameters.worldViewProjection = rendererInfo.worldViewProjection;
        techniqueParameters.lightPosition = rendererInfo.lightPosition;

        var techniqueName = (this).technique.name;
        if (techniqueName.indexOf("flat") === -1 && techniqueName.indexOf("lambert") === -1) {
            techniqueParameters.eyePosition = rendererInfo.eyePosition;
        }

        var skinController = geometryInstance.skinController;
        if (skinController) {
            techniqueParameters.skinBones = skinController.output;
            if (skinController.index === undefined) {
                skinController.index = DefaultRendering.nextSkinID;
                DefaultRendering.nextSkinID += 1;
            }
            drawParameters.sortKey = -renderingCommonSortKeyFn((this).techniqueIndex, skinController.index, sharedMaterial.meta.materialIndex);
        } else {
            drawParameters.sortKey = renderingCommonSortKeyFn((this).techniqueIndex, sharedMaterial.meta.materialIndex, rendererInfo.id);
        }

        geometryInstance.renderUpdate = (this).update;
    };

    DefaultRendering.create = //
    // Constructor function
    //
    function (gd, md, shaderManager, effectsManager) {
        var dr = new DefaultRendering();

        dr.md = md;
        dr.sm = shaderManager;

        dr.lightPositionUpdated = true;
        dr.lightPosition = md.v3Build(1000.0, 1000.0, 0.0);
        dr.eyePositionUpdated = true;
        dr.eyePosition = md.v3BuildZero();

        dr.globalTechniqueParameters = gd.createTechniqueParameters({
            lightColor: md.v3BuildOne(),
            ambientColor: md.v3Build(0.2, 0.2, 0.3),
            time: 0.0
        });
        dr.globalTechniqueParametersArray = [dr.globalTechniqueParameters];

        dr.passes = [[], [], []];

        var onShaderLoaded = function onShaderLoadedFn(shader) {
            var skinBones = shader.getParameter("skinBones");
            dr.defaultSkinBufferSize = skinBones.rows * skinBones.columns;
        };

        shaderManager.load("shaders/defaultrendering.cgfx", onShaderLoaded);
        shaderManager.load("shaders/debug.cgfx");

        // Update effects
        var updateNodeRendererInfo = function updateNodeRendererInfoFn(node, rendererInfo, camera) {
            var lightPositionUpdated = dr.lightPositionUpdated;
            var eyePositionUpdated = dr.eyePositionUpdated;
            var matrix = node.world;
            if (rendererInfo.worldUpdate !== node.worldUpdate) {
                rendererInfo.worldUpdate = node.worldUpdate;
                lightPositionUpdated = true;
                eyePositionUpdated = true;
                rendererInfo.worldInverse = md.m43Inverse(matrix, rendererInfo.worldInverse);
            }
            if (lightPositionUpdated) {
                rendererInfo.lightPosition = md.m43TransformPoint(rendererInfo.worldInverse, dr.lightPosition, rendererInfo.lightPosition);
            }
            if (eyePositionUpdated) {
                rendererInfo.eyePosition = md.m43TransformPoint(rendererInfo.worldInverse, dr.eyePosition, rendererInfo.eyePosition);
            }
            rendererInfo.worldViewProjection = md.m43MulM44(matrix, camera.viewProjectionMatrix, rendererInfo.worldViewProjection);
        };

        var defaultUpdate = function defaultUpdateFn(camera) {
            var node = this.node;
            var rendererInfo = node.rendererInfo;
            if (rendererInfo.frameVisible !== node.frameVisible) {
                rendererInfo.frameVisible = node.frameVisible;
                updateNodeRendererInfo(node, rendererInfo, camera);
            }
        };

        var defaultSkinnedUpdate = function defaultSkinnedUpdateFn(camera) {
            var node = this.node;
            var rendererInfo = node.rendererInfo;
            if (rendererInfo.frameVisible !== node.frameVisible) {
                rendererInfo.frameVisible = node.frameVisible;
                updateNodeRendererInfo(node, rendererInfo, camera);
            }

            var skinController = this.skinController;
            if (skinController) {
                skinController.update();
            }
        };

        var debugUpdate = function debugUpdateFn(camera) {
            var matrix = this.node.world;
            var tp = this.techniqueParameters;
            tp.worldViewProjection = md.m43MulM44(matrix, camera.viewProjectionMatrix, tp.worldViewProjection);
            tp.worldInverseTranspose = md.m33InverseTranspose(matrix, tp.worldInverseTranspose);
        };

        var debugSkinnedUpdate = function debugSkinnedUpdateFn(camera) {
            var matrix = this.node.world;
            var tp = this.techniqueParameters;
            tp.worldViewProjection = md.m43MulM44(matrix, camera.viewProjectionMatrix, tp.worldViewProjection);
            tp.worldInverseTranspose = md.m33InverseTranspose(matrix, tp.worldInverseTranspose);

            var skinController = this.skinController;
            if (skinController) {
                skinController.update();
            }
        };

        var defaultEnvUpdate = function defaultEnvUpdateFn(camera) {
            var node = this.node;
            var rendererInfo = node.rendererInfo;
            if (rendererInfo.frameVisible !== node.frameVisible) {
                rendererInfo.frameVisible = node.frameVisible;
                updateNodeRendererInfo(node, rendererInfo, camera);
            }
            if (rendererInfo.worldUpdateEnv !== node.worldUpdate) {
                rendererInfo.worldUpdateEnv = node.worldUpdate;
                var matrix = node.world;
                rendererInfo.worldInverseTranspose = md.m33InverseTranspose(matrix, rendererInfo.worldInverseTranspose);
            }

            var techniqueParameters = this.techniqueParameters;
            techniqueParameters.worldInverseTranspose = rendererInfo.worldInverseTranspose;
        };

        var defaultEnvSkinnedUpdate = function defaultEnvSkinnedUpdateFn(camera) {
            defaultEnvUpdate.call(this, camera);

            var skinController = this.skinController;
            if (skinController) {
                skinController.update();
            }
        };

        // Prepare
        var debugLinesPrepare = function debugLinesPrepareFn(geometryInstance) {
            DefaultRendering.defaultPrepareFn.call(this, geometryInstance);
            var techniqueParameters = geometryInstance.techniqueParameters;
            techniqueParameters.constantColor = geometryInstance.sharedMaterial.meta.constantColor;
        };

        var defaultPrepare = function defaultPrepareFn(geometryInstance) {
            DefaultRendering.defaultPrepareFn.call(this, geometryInstance);

            //For untextured objects we need to choose a technique that uses materialColor instead.
            var techniqueParameters = geometryInstance.sharedMaterial.techniqueParameters;
            var diffuse = techniqueParameters.diffuse;
            if (diffuse === undefined) {
                if (!techniqueParameters.materialColor) {
                    techniqueParameters.materialColor = md.v4BuildOne();
                }
            } else if (diffuse.length === 4) {
                techniqueParameters.materialColor = md.v4Build.apply(md, diffuse);
                diffuse = techniqueParameters.diffuse_map;
                techniqueParameters.diffuse = diffuse;
            }
            if (!diffuse) {
                var shader = shaderManager.get("shaders/defaultrendering.cgfx");
                if (geometryInstance.geometryType === "skinned") {
                    geometryInstance.drawParameters[0].technique = shader.getTechnique("flat_skinned");
                } else {
                    geometryInstance.drawParameters[0].technique = shader.getTechnique("flat");
                }
            }
        };

        var noDiffusePrepare = function noDiffusePrepareFn(geometryInstance) {
            DefaultRendering.defaultPrepareFn.call(this, geometryInstance);

            //For untextured objects we need to choose a technique that uses materialColor instead.
            var techniqueParameters = geometryInstance.sharedMaterial.techniqueParameters;
            var diffuse = techniqueParameters.diffuse;
            if (diffuse === undefined) {
                if (!techniqueParameters.materialColor) {
                    techniqueParameters.materialColor = md.v4BuildOne();
                }
            } else if (diffuse.length === 4) {
                techniqueParameters.materialColor = md.v4Build.apply(md, diffuse);
                techniqueParameters.diffuse = undefined;
            }
        };

        var loadTechniques = function loadTechniquesFn(shaderManager) {
            var that = this;

            var callback = function shaderLoadedCallbackFn(shader) {
                that.shader = shader;
                that.technique = shader.getTechnique(that.techniqueName);
                that.techniqueIndex = that.technique.id;
            };
            shaderManager.load(this.shaderName, callback);
        };

        dr.defaultPrepareFn = defaultPrepare;
        dr.defaultUpdateFn = defaultUpdate;
        dr.defaultSkinnedUpdateFn = defaultSkinnedUpdate;
        dr.loadTechniquesFn = loadTechniques;

        var effect;
        var effectTypeData;
        var skinned = "skinned";
        var rigid = "rigid";

        // Register the effects
        //
        // constant
        //
        effect = Effect.create("constant");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "flat",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "flat_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // constant_nocull
        //
        effect = Effect.create("constant_nocull");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "flat_nocull",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "flat_skinned_nocull",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // lambert
        //
        effect = Effect.create("lambert");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "lambert",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "lambert_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // blinn
        //
        effect = Effect.create("blinn");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blinn",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blinn_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // blinn_nocull
        //
        effect = Effect.create("blinn_nocull");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blinn_nocull",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blinn_skinned_nocull",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // phong
        //
        effect = Effect.create("phong");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "phong",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "phong_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // debug_lines_constant
        //
        effect = Effect.create("debug_lines_constant");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: debugLinesPrepare,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_lines_constant",
            update: debugUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // debug_normals
        //
        effect = Effect.create("debug_normals");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_normals",
            update: debugUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_normals_skinned",
            update: debugSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // debug_tangents
        //
        effect = Effect.create("debug_tangents");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_tangents",
            update: debugUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_tangents_skinned",
            update: debugSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // debug_binormals
        //
        effect = Effect.create("debug_binormals");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_binormals",
            update: debugUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: DefaultRendering.defaultPrepareFn,
            shaderName: "shaders/debug.cgfx",
            techniqueName: "debug_binormals_skinned",
            update: debugSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap
        //
        effect = Effect.create("normalmap");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap_specularmap
        //
        effect = Effect.create("normalmap_specularmap");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap_specularmap_alphamap
        //
        effect = Effect.create("normalmap_specularmap_alphamap");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_alphamap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // normalmap_alphatest
        //
        effect = Effect.create("normalmap_alphatest");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_alphatest",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_alphatest_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap_specularmap_alphatest
        //
        effect = Effect.create("normalmap_specularmap_alphatest");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_alphatest",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_alphatest_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap_glowmap
        //
        effect = Effect.create("normalmap_glowmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_glowmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_glowmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // normalmap_specularmap_glowmap
        //
        effect = Effect.create("normalmap_specularmap_glowmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_glowmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "normalmap_specularmap_glowmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap
        //
        effect = Effect.create("rxgb_normalmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap_specularmap
        //
        effect = Effect.create("rxgb_normalmap_specularmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap_alphatest
        //
        effect = Effect.create("rxgb_normalmap_alphatest");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_alphatest",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_alphatest_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap_specularmap_alphatest
        //
        effect = Effect.create("rxgb_normalmap_specularmap_alphatest");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap_alphatest",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap_alphatest_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap_glowmap
        //
        effect = Effect.create("rxgb_normalmap_glowmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_glowmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_glowmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // rxgb_normalmap_specularmap_glowmap
        //
        effect = Effect.create("rxgb_normalmap_specularmap_glowmap");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap_glowmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "rxgb_normalmap_specularmap_glowmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // add
        //
        effect = Effect.create("add");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "add",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "add_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // add_particle
        //
        effect = Effect.create("add_particle");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "add_particle",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // blend
        //
        effect = Effect.create("blend");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blend",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blend_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // blend_particle
        //
        effect = Effect.create("blend_particle");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "blend_particle",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // translucent
        //
        effect = Effect.create("translucent");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "translucent",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "translucent_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // translucent_particle
        //
        effect = Effect.create("translucent_particle");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "translucent_particle",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // filter
        //
        effect = Effect.create("filter");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "filter",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "filter_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // invfilter
        //
        effect = Effect.create("invfilter");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "invfilter",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // invfilter_particle
        //
        effect = Effect.create("invfilter_particle");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "invfilter_particle",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // glass
        //
        effect = Effect.create("glass");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "glass",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // glass_env
        //
        effect = Effect.create("glass_env");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "glass_env",
            update: defaultEnvUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // modulate2
        //
        effect = Effect.create("modulate2");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "modulate2",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "modulate2_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // skybox
        //
        effect = Effect.create("skybox");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "skybox",
            update: defaultEnvUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        //
        // env
        //
        effect = Effect.create("env");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "env",
            update: defaultEnvUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "env_skinned",
            update: defaultEnvSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // flare
        //
        effect = Effect.create("flare");
        effectsManager.add(effect);
        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "add",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectsManager.map("default", "blinn");

        //
        // glowmap
        //
        effect = Effect.create("glowmap");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "glowmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        effectTypeData = {
            prepare: noDiffusePrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "glowmap_skinned",
            update: defaultSkinnedUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(skinned, effectTypeData);

        //
        // lightmap
        //
        effect = Effect.create("lightmap");
        effectsManager.add(effect);

        effectTypeData = {
            prepare: defaultPrepare,
            shaderName: "shaders/defaultrendering.cgfx",
            techniqueName: "lightmap",
            update: defaultUpdate,
            loadTechniques: loadTechniques
        };
        effectTypeData.loadTechniques(shaderManager);
        effect.add(rigid, effectTypeData);

        return dr;
    };
    DefaultRendering.version = 1;

    DefaultRendering.numPasses = 3;

    DefaultRendering.passIndex = {
        opaque: 0,
        decal: 1,
        transparent: 2
    };

    DefaultRendering.nextRenderinfoID = 0;
    DefaultRendering.nextSkinID = 0;

    DefaultRendering.v4One = new Float32Array([1.0, 1.0, 1.0, 1.0]);
    DefaultRendering.identityUVTransform = new Float32Array([1, 0, 0, 1, 0, 0]);
    return DefaultRendering;
})();

// Copyright (c) 2010-2014 Turbulenz Limited
/*global TurbulenzEngine:false*/
/*global VMath:false*/
;

;

;

//
// ResourceLoader
//
var ResourceLoader = (function () {
    function ResourceLoader() {
    }
    //
    // clear
    //
    ResourceLoader.prototype.clear = function () {
        this.nodesMap = {};
        this.referencesPending = {};
        this.numReferencesPending = 0;
        this.animationsPending = {};
    };

    //
    // endLoading
    //
    ResourceLoader.prototype.endLoading = function (onload) {
        this.referencesPending = {};
        this.animationsPending = {};

        if (onload) {
            onload(this.data);
        }
    };

    ResourceLoader.prototype.resolveShapes = function (loadParams) {
        var copyObject = function copyObjectFn(o) {
            var newObj = {};
            for (var p in o) {
                if (o.hasOwnProperty(p)) {
                    newObj[p] = o[p];
                }
            }
            return newObj;
        };

        var shapesNamePrefix = loadParams.shapesNamePrefix;

        // we reuse shapesNamePrefix to save adding prefixes for everything
        var skeletonNamePrefix = loadParams.shapesNamePrefix;
        var sceneData = loadParams.data;
        var fileShapes = sceneData.geometries;
        var targetShapes = this.data.geometries;
        if (!targetShapes) {
            targetShapes = {};
            this.data.geometries = targetShapes;
        }

        for (var fileShapeName in fileShapes) {
            if (fileShapes.hasOwnProperty(fileShapeName)) {
                var fileShape = fileShapes[fileShapeName];
                var targetShapeName = (shapesNamePrefix ? (shapesNamePrefix + "-" + fileShapeName) : fileShapeName);

                // Update the skeleton reference
                var fileSkeletonName = fileShape.skeleton;
                if (fileSkeletonName) {
                    // the shape has to be copied if it has a skeleton as the same shape
                    // can be used with multiple skeletons
                    targetShapes[targetShapeName] = copyObject(fileShape);
                    targetShapes[targetShapeName].skeleton = (skeletonNamePrefix ? (skeletonNamePrefix + "-" + fileSkeletonName) : fileSkeletonName);
                } else {
                    targetShapes[targetShapeName] = fileShape;
                }
            }
        }
    };

    ResourceLoader.prototype.resolveSkeletons = function (loadParams) {
        // we reuse shapesNamePrefix to save adding prefixes for everything
        var skeletonNamePrefix = loadParams.shapesNamePrefix;
        var sceneData = loadParams.data;
        var fileSkeletons = sceneData.skeletons;
        var targetSkeletons = this.data.skeletons;
        if (!targetSkeletons) {
            targetSkeletons = {};
            this.data.skeletons = targetSkeletons;
        }

        for (var fileSkeletonName in fileSkeletons) {
            if (fileSkeletons.hasOwnProperty(fileSkeletonName)) {
                var fileSkeleton = fileSkeletons[fileSkeletonName];
                var targetSkeletonName = (skeletonNamePrefix ? (skeletonNamePrefix + "-" + fileSkeletonName) : fileSkeletonName);
                targetSkeletons[targetSkeletonName] = fileSkeleton;
            }
        }
    };

    //
    // Resolve animations
    //
    ResourceLoader.prototype.resolveAnimations = function (loadParams) {
        var sceneData = loadParams.data;

        var fileAnims = sceneData.animations;
        if (!fileAnims) {
            return;
        }

        var currentLoader = this;
        var anims = currentLoader.data.animations;
        if (!anims) {
            anims = {};
            currentLoader.data.animations = anims;
        }

        var postLoadReference = function postLoadReferenceFn(sceneText) {
            if (sceneText) {
                var sceneData = JSON.parse(sceneText);
                var animations = sceneData.animations;
                for (var anim in animations) {
                    if (animations.hasOwnProperty(anim)) {
                        anims[anim] = animations[anim];
                    }
                }
            }

            //Utilities.log("resolved ref for " + anim + " count now " + (currentLoader.numReferencesPending-1));
            currentLoader.numReferencesPending -= 1;
            if (currentLoader.numReferencesPending <= 0) {
                currentLoader.endLoading(loadParams.onload);
            }
        };

        // Import animations
        var requestOwner = (loadParams.request ? loadParams : TurbulenzEngine);
        for (var a in fileAnims) {
            if (fileAnims.hasOwnProperty(a)) {
                var reference = fileAnims[a].reference;
                if (reference) {
                    if (!this.animationsPending[a]) {
                        this.animationsPending[a] = true;
                        this.numReferencesPending += 1;

                        //Utilities.log("adding ref for " + a + " count now " + this.numReferencesPending);
                        delete fileAnims[a].reference;

                        loadParams.requestHandler.request({
                            src: reference,
                            requestOwner: requestOwner,
                            onload: postLoadReference
                        });
                    }
                } else {
                    anims[a] = fileAnims[a];
                }
            }
        }
    };

    //
    // resolveNodes
    //
    ResourceLoader.prototype.resolveNodes = function (loadParams) {
        var sceneData = loadParams.data;

        var references = this.referencesPending;
        var numReferences = 0;
        var nodesMap = this.nodesMap;

        var currentLoader = this;

        var nodesNamePrefix = loadParams.nodesNamePrefix;
        var shapesNamePrefix = loadParams.shapesNamePrefix;

        var requestOwner = (loadParams.request ? loadParams : TurbulenzEngine);

        var copyObject = function copyObjectFn(o) {
            var newObj = {};
            for (var p in o) {
                if (o.hasOwnProperty(p)) {
                    newObj[p] = o[p];
                }
            }
            return newObj;
        };

        var resolveNode = function resolveNodeFn(fileNode, nodeName, parentNodePath) {
            // We're changing a node which may be referenced multiple
            // times so take a copy
            var node = (copyObject(fileNode));
            var nodePath = parentNodePath ? (parentNodePath + "/" + nodeName) : nodeName;

            var reference = node.reference;
            if (reference) {
                //Utilities.log("Reference resolve for " + nodePath);
                var internalReferenceIndex = reference.indexOf("#");
                if (internalReferenceIndex === -1) {
                    var referenceParameters = references[reference];
                    if (!referenceParameters || referenceParameters.length === 0 || !node.inplace) {
                        numReferences += 1;

                        //Utilities.log("adding ref for " + nodePath + " numrefs now " + numReferences);
                        var sceneParameters = copyObject(loadParams);
                        sceneParameters.append = true;
                        if (node.inplace) {
                            sceneParameters.nodesNamePrefix = parentNodePath;
                            sceneParameters.shapesNamePrefix = null;
                            sceneParameters.parentNode = null;
                        } else {
                            sceneParameters.nodesNamePrefix = nodePath;
                            sceneParameters.shapesNamePrefix = reference;
                            sceneParameters.parentNode = node;
                        }
                        if (node.skin) {
                            sceneParameters.skin = node.skin;
                        }

                        if (!referenceParameters || referenceParameters.length === 0) {
                            referenceParameters = [sceneParameters];
                            references[reference] = referenceParameters;

                            var loadReference = function (sceneText) {
                                var numInstances = referenceParameters.length;
                                var sceneData;
                                if (sceneText) {
                                    sceneData = JSON.parse(sceneText);
                                } else {
                                    // Make sure we can call scene
                                    // load to correctly deal with
                                    // reference counts when a
                                    // reference is missing
                                    sceneData = {};
                                }
                                var params;
                                for (var n = 0; n < numInstances; n += 1) {
                                    params = referenceParameters[n];
                                    params.data = sceneData;
                                    params.isReference = true;
                                    currentLoader.resolve(params);
                                }
                                referenceParameters.length = 0;
                            };

                            loadParams.requestHandler.request({
                                src: reference,
                                requestOwner: requestOwner,
                                onload: loadReference
                            });
                        } else {
                            referenceParameters.push(sceneParameters);
                        }
                    }
                }
                delete node.reference;
                delete node.inplace;
            }

            var geometryinstances = node.geometryinstances;
            if (shapesNamePrefix && geometryinstances) {
                // Need to deep copy the geometry instances dictionary because we're prefixing the names
                node.geometryinstances = {};
                for (var gi in geometryinstances) {
                    if (geometryinstances.hasOwnProperty(gi)) {
                        node.geometryinstances[gi] = copyObject(geometryinstances[gi]);
                        var geometryInstance = node.geometryinstances[gi];

                        //Utilities.log("prefixing " + geometryInstance.geometry + " with " + shapesNamePrefix);
                        geometryInstance.geometry = shapesNamePrefix + "-" + geometryInstance.geometry;
                    }
                }
            }

            var fileChildren = fileNode.nodes;
            if (fileChildren) {
                node.nodes = {};
                for (var c in fileChildren) {
                    if (fileChildren.hasOwnProperty(c)) {
                        var childPath = nodePath + "/" + c;
                        if (!nodesMap[childPath]) {
                            node.nodes[c] = resolveNode(fileChildren[c], c, nodePath);
                            nodesMap[childPath] = node.nodes[c];
                        }
                    }
                }
            }

            return node;
        };

        var fileNodes = sceneData.nodes;
        var parentNode = loadParams.parentNode;
        for (var fn in fileNodes) {
            if (fileNodes.hasOwnProperty(fn) && fileNodes[fn]) {
                var nodeName = fn;
                var fileNode = resolveNode(fileNodes[fn], nodeName, nodesNamePrefix);
                var nodePath = (nodesNamePrefix ? (nodesNamePrefix + "/" + fn) : fn);
                var overloadedNode = nodesMap[nodePath];

                if (overloadedNode) {
                    //Utilities.log("Overloaded node '" + nodePath + "'");
                    var overloadedMatrix = overloadedNode.matrix;
                    if (overloadedMatrix && fileNode.matrix) {
                        overloadedNode.matrix = VMath.m43Mul(fileNode.matrix, overloadedMatrix);
                        overloadedMatrix = null;
                    }

                    var overloadedChildren = overloadedNode.nodes;
                    if (overloadedChildren && fileNode.nodes) {
                        for (var c in fileNode.nodes) {
                            if (fileNode.nodes.hasOwnProperty(c)) {
                                overloadedChildren[c] = fileNode.nodes[c];
                            }
                        }
                    } else if (fileNode.nodes) {
                        overloadedNode.nodes = fileNode.nodes;
                    }

                    for (var on in fileNode) {
                        if (fileNode.hasOwnProperty(on)) {
                            overloadedNode[on] = fileNode[on];
                        }
                    }
                    fileNode = overloadedNode;
                } else {
                    if (loadParams.isReference && parentNode) {
                        if (!parentNode.nodes) {
                            parentNode.nodes = {};
                        }
                        parentNode.nodes[fn] = fileNode;
                    } else {
                        this.data.nodes[fn] = fileNode;
                    }

                    nodesMap[nodePath] = fileNode;
                }
            }
        }

        this.numReferencesPending += numReferences;
        //Utilities.log("total refs now " + this.numReferencesPending);
    };

    //
    // loadPhysicsNodes
    //
    ResourceLoader.prototype.resolvePhysicsNodes = function (loadParams) {
        var sceneData = loadParams.data;
        var nodesNamePrefix = loadParams.nodesNamePrefix;
        var shapesNamePrefix = loadParams.shapesNamePrefix;

        function begetFn(o) {
            var F = function () {
            };
            F.prototype = o;
            return new F();
        }

        var fileModels = sceneData.physicsmodels;
        var targetFileModels = this.data.physicsmodels;
        if (!targetFileModels) {
            targetFileModels = {};
            this.data.physicsmodels = targetFileModels;
        }

        for (var fm in fileModels) {
            if (fileModels.hasOwnProperty(fm)) {
                var fileModel = fileModels[fm];

                if (shapesNamePrefix) {
                    var newModelName = shapesNamePrefix ? shapesNamePrefix + "-" + fm : fm;

                    var model = begetFn(fileModel);
                    targetFileModels[newModelName] = model;

                    var geometry = model.geometry;
                    if (geometry) {
                        model.geometry = shapesNamePrefix ? shapesNamePrefix + "-" + geometry : geometry;
                    }
                } else {
                    targetFileModels[fm] = fileModel;
                }
            }
        }

        var fileNodes = sceneData.physicsnodes;
        var targetFileNodes = this.data.physicsnodes;
        if (!targetFileNodes) {
            targetFileNodes = {};
            this.data.physicsnodes = targetFileNodes;
        }

        for (var fn in fileNodes) {
            if (fileNodes.hasOwnProperty(fn)) {
                var fileNode = fileNodes[fn];

                if (nodesNamePrefix || shapesNamePrefix) {
                    var targetName = fileNode.target;
                    targetName = nodesNamePrefix ? (nodesNamePrefix + "/" + targetName) : targetName;

                    var node = begetFn(fileNode);
                    node.target = targetName;

                    node.body = shapesNamePrefix ? shapesNamePrefix + "-" + fileNode.body : fileNode.body;

                    var newNodeName = nodesNamePrefix ? (nodesNamePrefix + "/" + fn) : fn;
                    targetFileNodes[newNodeName] = node;
                } else {
                    targetFileNodes[fn] = fileNode;
                }
            }
        }
    };

    //
    // loadAreas
    //
    ResourceLoader.prototype.resolveAreas = function (loadParams) {
        var sceneData = loadParams.data;

        var fileAreas = sceneData.areas;
        if (!fileAreas) {
            return;
        }

        var numFileAreas = fileAreas.length;
        if (numFileAreas <= 0) {
            return;
        }

        var targetAreas = this.data.areas;
        if (!targetAreas) {
            targetAreas = [];
            this.data.areas = targetAreas;
        }

        var nodesNamePrefix = loadParams.nodesNamePrefix;

        for (var fa = 0; fa < numFileAreas; fa += 1) {
            var fileArea = fileAreas[fa];

            if (nodesNamePrefix) {
                var targetName = fileArea.target;
                fileArea.target = (nodesNamePrefix + "/" + targetName);
            }
            targetAreas.push(fileArea);
        }
    };

    //
    // resolve
    //
    ResourceLoader.prototype.resolve = function (loadParams) {
        if (!loadParams.append) {
            this.data = { nodes: {} };
        }

        // Start by simply copying any dictionaries which we don't special case
        var appendData = loadParams.data;
        for (var d in appendData) {
            if (d !== "nodes" && d !== "skeletons" && d !== "geometries" && d !== "animations" && d !== "areas" && d !== "physicsnodes" && d !== "physicsmodels") {
                if (appendData.hasOwnProperty(d)) {
                    var dict = appendData[d];
                    var targetDict = this.data[d];
                    if (!targetDict) {
                        this.data[d] = dict;
                    } else {
                        for (var e in dict) {
                            if (dict.hasOwnProperty(e) && !targetDict[e]) {
                                targetDict[e] = dict[e];
                            }
                        }
                    }
                }
            }
        }

        this.resolveShapes(loadParams);

        this.resolveSkeletons(loadParams);

        this.resolveAnimations(loadParams);

        this.resolveNodes(loadParams);

        this.resolvePhysicsNodes(loadParams);

        this.resolveAreas(loadParams);

        if (loadParams.isReference) {
            this.numReferencesPending -= 1;
            //Utilities.log("loaded ref now " + this.numReferencesPending);
        }

        if (this.numReferencesPending <= 0) {
            this.endLoading(loadParams.onload);
        }
    };

    //
    // load
    //
    ResourceLoader.prototype.load = function (assetPath, loadParams) {
        var loader = this;
        var dataReceived = function dataReceivedFn(text) {
            var sceneData = {};
            if (text) {
                sceneData = JSON.parse(text);
            }

            loadParams.data = sceneData;
            loadParams.append = false;
            loader.resolve(loadParams);
        };

        loadParams.requestHandler.request({
            src: assetPath,
            requestOwner: loadParams.request ? loadParams : TurbulenzEngine,
            onload: dataReceived
        });
    };

    ResourceLoader.create = // Constructor function
    function () {
        var rl = new ResourceLoader();
        rl.clear();

        rl.skeletonNames = {};

        return rl;
    };
    ResourceLoader.version = 1;
    return ResourceLoader;
})();

// Copyright (c) 2010-2014 Turbulenz Limited
/*global Scene*/
/*global Geometry*/
/*global GeometryInstance*/
/*global Utilities*/
/*global TurbulenzEngine*/
;

;

//
// Scene debugging methods
//
//
// getMaterialName
//
Scene.prototype.getMaterialName = function getMaterialNameFn(node) {
    var names = [];

    var materials = this.materials;
    function addMaterialName(material, nameList) {
        for (var m in materials) {
            if (materials.hasOwnProperty(m)) {
                if (material === materials[m]) {
                    nameList.push(m);
                }
            }
        }
    }

    var material = node.sharedMaterial;
    if (material) {
        addMaterialName(material, names);
    }

    var renderables = node.renderables;
    if (renderables) {
        var numRenderables = renderables.length;
        for (var i = 0; i < numRenderables; i += 1) {
            var renderable = renderables[i];
            material = renderable.sharedMaterial;
            addMaterialName(material, names);
        }
    }

    var lights = node.lightInstances;
    if (lights) {
        var numLights = lights.length;
        for (var l = 0; l < numLights; l += 1) {
            var light = lights[l];
            material = light.material;
            addMaterialName(material, names);
        }
    }

    if (names.length) {
        if (names.length === 1) {
            return names[0];
        } else {
            return names;
        }
    }

    return undefined;
};

//
// findLightName
//
Scene.prototype.findLightName = function findLightNameFn(light) {
    var lights = this.lights;
    for (var n in lights) {
        if (lights.hasOwnProperty(n)) {
            if (light === lights[n]) {
                return n;
            }
        }
    }
    return undefined;
};

//
// writeBox
//
Scene.prototype.writeBox = function sceneWriteBoxFn(writer, extents, r, g, b) {
    var p0 = extents[0];
    var p1 = extents[1];
    var p2 = extents[2];
    var n0 = extents[3];
    var n1 = extents[4];
    var n2 = extents[5];

    writer(p0, p1, p2, r, g, b);
    writer(p0, p1, n2, r, g, b);

    writer(p0, p1, p2, r, g, b);
    writer(p0, n1, p2, r, g, b);

    writer(p0, p1, p2, r, g, b);
    writer(n0, p1, p2, r, g, b);

    writer(n0, n1, n2, r, g, b);
    writer(n0, n1, p2, r, g, b);

    writer(n0, n1, n2, r, g, b);
    writer(n0, p1, n2, r, g, b);

    writer(n0, n1, n2, r, g, b);
    writer(p0, n1, n2, r, g, b);

    writer(p0, n1, n2, r, g, b);
    writer(p0, n1, p2, r, g, b);

    writer(p0, n1, n2, r, g, b);
    writer(p0, p1, n2, r, g, b);

    writer(n0, n1, p2, r, g, b);
    writer(p0, n1, p2, r, g, b);

    writer(n0, n1, p2, r, g, b);
    writer(n0, p1, p2, r, g, b);

    writer(n0, p1, n2, r, g, b);
    writer(p0, p1, n2, r, g, b);

    writer(n0, p1, n2, r, g, b);
    writer(n0, p1, p2, r, g, b);
};

//
// writeRotatedBox
//
Scene.prototype.writeRotatedBox = function sceneWriteRotatedBoxFn(writer, transform, halfExtents, r, g, b) {
    var m0 = transform[0];
    var m1 = transform[1];
    var m2 = transform[2];
    var m3 = transform[3];
    var m4 = transform[4];
    var m5 = transform[5];
    var m6 = transform[6];
    var m7 = transform[7];
    var m8 = transform[8];
    var m9 = transform[9];
    var m10 = transform[10];
    var m11 = transform[11];

    var hx = halfExtents[0];
    var hy = halfExtents[1];
    var hz = halfExtents[2];

    var hx0 = (m0 * hx);
    var hx1 = (m1 * hx);
    var hx2 = (m2 * hx);
    var hy3 = (m3 * hy);
    var hy4 = (m4 * hy);
    var hy5 = (m5 * hy);
    var hz6 = (m6 * hz);
    var hz7 = (m7 * hz);
    var hz8 = (m8 * hz);

    var p0x = (m9 - hx0 - hy3 - hz6);
    var p0y = (m10 - hx1 - hy4 - hz7);
    var p0z = (m11 - hx2 - hy5 - hz8);
    var p1x = (m9 + hx0 - hy3 - hz6);
    var p1y = (m10 + hx1 - hy4 - hz7);
    var p1z = (m11 + hx2 - hy5 - hz8);
    var p2x = (m9 + hx0 - hy3 + hz6);
    var p2y = (m10 + hx1 - hy4 + hz7);
    var p2z = (m11 + hx2 - hy5 + hz8);
    var p3x = (m9 - hx0 - hy3 + hz6);
    var p3y = (m10 - hx1 - hy4 + hz7);
    var p3z = (m11 - hx2 - hy5 + hz8);
    var p4x = (m9 - hx0 + hy3 - hz6);
    var p4y = (m10 - hx1 + hy4 - hz7);
    var p4z = (m11 - hx2 + hy5 - hz8);
    var p5x = (m9 + hx0 + hy3 - hz6);
    var p5y = (m10 + hx1 + hy4 - hz7);
    var p5z = (m11 + hx2 + hy5 - hz8);
    var p6x = (m9 + hx0 + hy3 + hz6);
    var p6y = (m10 + hx1 + hy4 + hz7);
    var p6z = (m11 + hx2 + hy5 + hz8);
    var p7x = (m9 - hx0 + hy3 + hz6);
    var p7y = (m10 - hx1 + hy4 + hz7);
    var p7z = (m11 - hx2 + hy5 + hz8);

    writer(p0x, p0y, p0z, r, g, b);
    writer(p1x, p1y, p1z, r, g, b);

    writer(p1x, p1y, p1z, r, g, b);
    writer(p2x, p2y, p2z, r, g, b);

    writer(p2x, p2y, p2z, r, g, b);
    writer(p3x, p3y, p3z, r, g, b);

    writer(p3x, p3y, p3z, r, g, b);
    writer(p0x, p0y, p0z, r, g, b);

    writer(p0x, p0y, p0z, r, g, b);
    writer(p4x, p4y, p4z, r, g, b);

    writer(p1x, p1y, p1z, r, g, b);
    writer(p5x, p5y, p5z, r, g, b);

    writer(p2x, p2y, p2z, r, g, b);
    writer(p6x, p6y, p6z, r, g, b);

    writer(p3x, p3y, p3z, r, g, b);
    writer(p7x, p7y, p7z, r, g, b);

    writer(p4x, p4y, p4z, r, g, b);
    writer(p5x, p5y, p5z, r, g, b);

    writer(p5x, p5y, p5z, r, g, b);
    writer(p6x, p6y, p6z, r, g, b);

    writer(p6x, p6y, p6z, r, g, b);
    writer(p7x, p7y, p7z, r, g, b);

    writer(p7x, p7y, p7z, r, g, b);
    writer(p4x, p4y, p4z, r, g, b);
};

//
// drawLights
//
Scene.prototype.drawLights = function sceneDrawLightsFn(gd, sm, camera) {
    var visibleNodes = this.visibleNodes;
    var numVisibleNodes = visibleNodes.length;
    if (numVisibleNodes > 0) {
        var node, lights, numLights, lightInstance, light, l;
        var numSpot = 0;
        var numPoint = 0;
        var numFog = 0;
        var n = 0;
        do {
            node = visibleNodes[n];

            lights = node.lightInstances;
            if (lights) {
                numLights = lights.length;
                for (l = 0; l < numLights; l += 1) {
                    lightInstance = lights[l];
                    light = lightInstance.light;
                    if (light) {
                        if (light.global) {
                            n += 1;
                            continue;
                        }

                        if (light.spot) {
                            numSpot += 1;
                        } else if (light.fog) {
                            numFog += 1;
                        } else {
                            numPoint += 1;
                        }
                    }
                }
            }

            n += 1;
        } while(n < numVisibleNodes);

        if (0 === numPoint && 0 === numSpot && 0 === numFog) {
            return;
        }

        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        var techniqueParameters = this.debugLinesTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix
            });
            this.debugLinesTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPosCol();
        var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, ((24 * numPoint) + (16 * numSpot) + (24 * numFog)), [vformatFloat3, vformatFloat3], sem);
        if (writer) {
            var md = this.md;
            var matrix, color, r, g, b, halfExtents, center;

            n = 0;
            do {
                node = visibleNodes[n];

                lights = node.lightInstances;
                if (lights) {
                    numLights = lights.length;
                    for (l = 0; l < numLights; l += 1) {
                        lightInstance = lights[l];
                        light = lightInstance.light;
                        if (light) {
                            if (light.global) {
                                n += 1;
                                continue;
                            }

                            matrix = node.world;
                            color = light.color;
                            r = color[0];
                            g = color[1];
                            b = color[2];

                            if (light.spot) {
                                var transform = md.m33MulM43(light.frustum, matrix);
                                var p0 = md.m43TransformPoint(transform, md.v3Build(-1, -1, 1));
                                var p1 = md.m43TransformPoint(transform, md.v3Build(1, -1, 1));
                                var p2 = md.m43TransformPoint(transform, md.v3Build(-1, 1, 1));
                                var p3 = md.m43TransformPoint(transform, md.v3Build(1, 1, 1));
                                var st = md.m43Pos(matrix);
                                writer(st, r, g, b);
                                writer(p0, r, g, b);
                                writer(st, r, g, b);
                                writer(p1, r, g, b);
                                writer(st, r, g, b);
                                writer(p2, r, g, b);
                                writer(st, r, g, b);
                                writer(p3, r, g, b);
                                writer(p0, r, g, b);
                                writer(p1, r, g, b);
                                writer(p1, r, g, b);
                                writer(p3, r, g, b);
                                writer(p3, r, g, b);
                                writer(p2, r, g, b);
                                writer(p2, r, g, b);
                                writer(p0, r, g, b);
                            } else if (light.fog) {
                                halfExtents = light.halfExtents;
                                this.writeRotatedBox(writer, matrix, halfExtents, r, g, b);
                            } else {
                                halfExtents = light.halfExtents;
                                center = light.center;
                                if (center) {
                                    matrix = md.m43Offset(matrix, center);
                                }
                                this.writeRotatedBox(writer, matrix, halfExtents, r, g, b);
                            }
                        }
                    }
                }

                n += 1;
            } while(n < numVisibleNodes);

            gd.endDraw(writer);
        }
    }
};

//
// drawLightsExtents
//
Scene.prototype.drawLightsExtents = function sceneDrawLightsExtentsFn(gd, sm, camera) {
    var visibleLights = this.visibleLights;
    var numVisibleLights = visibleLights.length;
    if (numVisibleLights > 0) {
        var lightInstance, light;
        var numLights = 0;
        var n = 0;
        do {
            lightInstance = visibleLights[n];
            light = lightInstance.light;
            if (light) {
                if (light.global) {
                    n += 1;
                    continue;
                }

                numLights += 1;
            }

            n += 1;
        } while(n < numVisibleLights);

        if (0 === numLights) {
            return;
        }

        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        var techniqueParameters = this.debugLinesTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix
            });
            this.debugLinesTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPosCol();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, (24 * numLights), [
            gd.VERTEXFORMAT_FLOAT3,
            gd.VERTEXFORMAT_FLOAT3
        ], sem);
        if (writer) {
            var writeBox = this.writeBox;
            var extents, color, r, g, b;

            n = 0;
            do {
                lightInstance = visibleLights[n];
                light = lightInstance.light;
                if (light) {
                    if (light.global) {
                        n += 1;
                        continue;
                    }

                    extents = lightInstance.getWorldExtents();
                    if (extents) {
                        color = light.color;
                        r = color[0];
                        g = color[1];
                        b = color[2];

                        writeBox(writer, extents, r, g, b);
                    }
                }

                n += 1;
            } while(n < numVisibleLights);

            gd.endDraw(writer);
        }
    }
};

//
// drawLightsScreenExtents
//
Scene.prototype.drawLightsScreenExtents = function sceneDrawLightsScreenExtentsFn(gd, sm/*, camera */ ) {
    var visibleLights = this.visibleLights;
    var numVisibleLights = visibleLights.length;
    if (numVisibleLights > 0) {
        var lightInstance, light;
        var numLights = 0;
        var n = 0;
        do {
            lightInstance = visibleLights[n];
            light = lightInstance.light;
            if (light) {
                if (light.global) {
                    n += 1;
                    continue;
                }

                if (lightInstance.screenExtents) {
                    numLights += 1;
                }
            }

            n += 1;
        } while(n < numVisibleLights);

        if (0 === numLights) {
            return;
        }

        var shader = sm.load("shaders/generic2D.cgfx");
        var technique = shader.getTechnique("vertexColor2D");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        technique.clipSpace = this.md.v4Build(1.0, 1.0, 0.0, 0.0);

        var sem = this.getDebugSemanticsPosCol();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, (8 * numLights), [
            gd.VERTEXFORMAT_FLOAT2,
            gd.VERTEXFORMAT_FLOAT3
        ], sem);
        if (writer) {
            var screenExtents, minX, maxX, minY, maxY, color, r, g, b;

            n = 0;
            do {
                lightInstance = visibleLights[n];
                light = lightInstance.light;
                if (light) {
                    if (light.global) {
                        n += 1;
                        continue;
                    }

                    screenExtents = lightInstance.screenExtents;
                    if (screenExtents) {
                        minX = screenExtents[0];
                        minY = screenExtents[1];
                        maxX = screenExtents[2];
                        maxY = screenExtents[3];

                        color = light.color;
                        r = color[0];
                        g = color[1];
                        b = color[2];

                        writer(minX, minY, r, g, b);
                        writer(minX, maxY, r, g, b);

                        writer(minX, maxY, r, g, b);
                        writer(maxX, maxY, r, g, b);

                        writer(maxX, maxY, r, g, b);
                        writer(maxX, minY, r, g, b);

                        writer(maxX, minY, r, g, b);
                        writer(minX, minY, r, g, b);
                    }
                }

                n += 1;
            } while(n < numVisibleLights);

            gd.endDraw(writer);
        }
    }
};

//
// drawAreas
//
Scene.prototype.drawAreas = function sceneDrawAreasFn(gd, sm, camera) {
    var areas = this.areas;
    if (areas) {
        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines");
        if (!technique) {
            return;
        }

        var isInsidePlanesAABB = this.isInsidePlanesAABB;
        var frustumPlanes = this.frustumPlanes;
        var visibleAreas = [];
        var numVisibleAreas = 0;
        var area, n;
        var areaIndex = this.cameraAreaIndex;
        if (areaIndex >= 0) {
            visibleAreas[numVisibleAreas] = areaIndex;
            numVisibleAreas += 1;

            var visiblePortals = this.visiblePortals;
            var numVisiblePortals = visiblePortals.length;
            for (n = 0; n < numVisiblePortals; n += 1) {
                visibleAreas[numVisibleAreas] = visiblePortals[n].area;
                numVisibleAreas += 1;
            }
        } else {
            var numAreas = areas.length;
            for (n = 0; n < numAreas; n += 1) {
                area = areas[n];
                if (isInsidePlanesAABB(area.extents, frustumPlanes)) {
                    visibleAreas[numVisibleAreas] = n;
                    numVisibleAreas += 1;
                }
            }
        }

        if (!numVisibleAreas) {
            return;
        }

        gd.setTechnique(technique);

        var techniqueParameters = this.debugLinesTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix
            });
            this.debugLinesTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPosCol();
        var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, (24 * numVisibleAreas), [vformatFloat3, vformatFloat3], sem);
        if (writer) {
            var writeBox = this.writeBox;
            var r, g, b;

            for (n = 0; n < numVisibleAreas; n += 1) {
                areaIndex = visibleAreas[n];
                area = areas[areaIndex];

                var m = (areaIndex % 3);
                if (m === 0) {
                    r = 1;
                    g = 0;
                    b = 0;
                } else if (m === 1) {
                    r = 0;
                    g = 1;
                    b = 0;
                } else {
                    r = 0;
                    g = 0;
                    b = 1;
                }

                writeBox(writer, area.extents, r, g, b);
            }

            gd.endDraw(writer);
        }
    }
};

//
// drawPortals
//
Scene.prototype.drawPortals = function sceneDrawPortalsFn(gd, sm, camera) {
    var areas = this.areas;
    if (areas) {
        var numVertices, area, n, portals, numPortals, np, portal;
        var points, numPoints, p, pointA, pointB;

        // First all portals in white
        var portalsToRender = [];
        var numAreas = areas.length;

        numVertices = 0;
        for (n = 0; n < numAreas; n += 1) {
            area = areas[n];
            portals = area.portals;
            numPortals = portals.length;
            for (np = 0; np < numPortals; np += 1) {
                portal = portals[np];
                portalsToRender.push(portal);
                numVertices += 2 * (portal.points.length);
            }
        }

        if (!numVertices) {
            return;
        }

        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines_constant");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        var md = this.md;
        technique.worldViewProjection = camera.viewProjectionMatrix;
        technique.constantColor = md.v4Build(1.0, 1.0, 1.0, 1.0);

        var sem = this.getDebugSemanticsPos();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3], sem);
        if (writer) {
            var numPortalsToRender = portalsToRender.length;
            for (n = 0; n < numPortalsToRender; n += 1) {
                portal = portalsToRender[n];
                points = portal.points;
                numPoints = points.length;
                for (p = 0; p < numPoints; p += 1) {
                    pointA = (p > 0 ? points[p - 1] : points[numPoints - 1]);
                    pointB = points[p];
                    writer(pointA[0], pointA[1], pointA[2]);
                    writer(pointB[0], pointB[1], pointB[2]);
                }
            }

            gd.endDraw(writer);
        }

        // Now redraw visible ones in yellow
        // It has to be done in this order because portals pointing in oposite directions will have the same points
        var visiblePortals = this.visiblePortals;
        var numVisiblePortals = visiblePortals.length;

        numVertices = 0;
        for (n = 0; n < numVisiblePortals; n += 1) {
            portal = visiblePortals[n].portal;
            numVertices += 2 * (portal.points.length);
        }

        if (!numVertices) {
            return;
        }

        technique.constantColor = md.v4Build(1.0, 1.0, 0.0, 1.0);

        writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3], sem);
        if (writer) {
            for (n = 0; n < numVisiblePortals; n += 1) {
                portal = visiblePortals[n].portal;
                points = portal.points;
                numPoints = points.length;
                for (p = 0; p < numPoints; p += 1) {
                    pointA = (p > 0 ? points[p - 1] : points[numPoints - 1]);
                    pointB = points[p];
                    writer(pointA[0], pointA[1], pointA[2]);
                    writer(pointB[0], pointB[1], pointB[2]);
                }
            }

            gd.endDraw(writer);
        }
    }
};

//
// drawTransforms
//
Scene.prototype.drawTransforms = function sceneDrawTransformsFn(gd, sm, camera, scale) {
    var nodes = this.visibleNodes;
    var numNodes = nodes.length;
    if (numNodes) {
        var n, numVertices = 6 * (numNodes + 1);

        if (!numVertices) {
            return;
        }

        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        var techniqueParameters = this.debugLinesTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix
            });
            this.debugLinesTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPosCol();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3, gd.VERTEXFORMAT_FLOAT3], sem);
        if (writer) {
            for (n = 0; n < numNodes; n += 1) {
                var node = nodes[n];
                var matrix = node.world;
                var px = matrix[9];
                var py = matrix[10];
                var pz = matrix[11];

                writer(px, py, pz, 1, 0, 0);
                writer(px + matrix[0] * scale, py + matrix[1] * scale, pz + matrix[2] * scale, 1, 0, 0);
                writer(px, py, pz, 0, 1, 0);
                writer(px + matrix[3] * scale, py + matrix[4] * scale, pz + matrix[5] * scale, 0, 1, 0);
                writer(px, py, pz, 0, 0, 1);
                writer(px + matrix[6] * scale, py + matrix[7] * scale, pz + matrix[8] * scale, 0, 0, 1);
            }

            writer(0, 0, 0, 0, 0, 0);
            writer(scale, 0, 0, 1, 0, 0);
            writer(0, 0, 0, 0, 0, 0);
            writer(0, scale, 0, 0, 1, 0);
            writer(0, 0, 0, 0, 0, 0);
            writer(0, 0, scale, 0, 0, 1);

            gd.endDraw(writer);

            writer = null;
        }
    }
};

//
// drawAnimationHierarchy
//
Scene.prototype.drawAnimationHierarchy = function sceneDrawAnimationHierarchyFn(gd, sm, camera, hierarchy, numJoints, controller, matrix, boneColor, boundsColor) {
    var numBones = numJoints;
    var interp = controller;
    var bounds = interp.bounds;
    var numVertices = 2 * numBones;
    var rBone = 0;
    var gBone = 0;
    var bBone = 0;
    var rBound = 1;
    var gBound = 0;
    var bBound = 0;
    if (boneColor) {
        rBone = boneColor[0];
        gBone = boneColor[1];
        bBone = boneColor[2];
    }
    if (boundsColor) {
        rBound = boundsColor[0];
        gBound = boundsColor[1];
        bBound = boundsColor[2];
    }
    if (bounds) {
        numVertices += 24;
    }

    if (!numVertices) {
        return;
    }

    var shader = sm.load("shaders/debug.cgfx");
    var technique = shader.getTechnique("debug_lines");
    if (!technique) {
        return;
    }

    gd.setTechnique(technique);

    var techniqueParameters = this.debugLinesTechniqueParameters;
    if (!techniqueParameters) {
        techniqueParameters = gd.createTechniqueParameters({
            worldViewProjection: camera.viewProjectionMatrix
        });
        this.debugLinesTechniqueParameters = techniqueParameters;
    } else {
        techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
    }

    gd.setTechniqueParameters(techniqueParameters);

    var sem = this.getDebugSemanticsPosCol();
    var writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3, gd.VERTEXFORMAT_FLOAT3], sem);
    if (writer) {
        var md = this.md;
        var m43TransformPoint = md.m43TransformPoint;
        var m43FromRTS = md.m43FromRTS;
        var m43FromQuatPos = md.m43FromQuatPos;
        var quatPosBuild = md.quatPosBuild;
        var m43Mul = md.m43Mul;
        var m43Pos = md.m43Pos;

        var jointParents = hierarchy.parents;
        interp.update();
        var interpOut = interp.output;
        var interpOutputChannels = interp.outputChannels;
        var hasScale = interpOutputChannels.scale;
        var ltms = [];
        var bone_matrix, quatPos;
        for (var n = 0; n < numBones; n += 1) {
            var parent = jointParents[n];

            var interpVal = interpOut[n];
            if (hasScale) {
                bone_matrix = m43FromRTS.call(md, interpVal.rotation, interpVal.translation, interpVal.scale, ltms[n]);
            } else {
                quatPos = quatPosBuild.call(md, interpVal.rotation, interpVal.translation, quatPos);
                bone_matrix = m43FromQuatPos.call(md, quatPos, ltms[n]);
            }

            if (parent !== -1) {
                bone_matrix = m43Mul.call(md, bone_matrix, ltms[parent], ltms[n]);
            }
            ltms[n] = bone_matrix;

            if (parent === -1) {
                continue;
            }

            var start = m43Pos.call(md, ltms[n]);
            var end = m43Pos.call(md, ltms[parent]);

            if (matrix) {
                start = m43TransformPoint.call(md, matrix, start);
                end = m43TransformPoint.call(md, matrix, end);
            }

            writer(start, rBone, gBone, bBone);
            writer(end, rBone, gBone, bBone);
        }

        if (bounds) {
            var center = bounds.center;
            var halfExtent = bounds.halfExtent;
            if (matrix) {
                center = md.v3Add(md.m43Pos(matrix), center);
            }

            var minExtent = md.v3Sub(center, halfExtent);
            var maxExtent = md.v3Add(center, halfExtent);
            var extents = [minExtent[0], minExtent[1], minExtent[2], maxExtent[0], maxExtent[1], maxExtent[2]];
            this.writeBox(writer, extents, rBound, gBound, bBound);
        }

        gd.endDraw(writer);
    }
};

//
// drawSceneNodeHierarchy
//
Scene.prototype.drawSceneNodeHierarchy = function drawSceneNodeHierarchy(gd, sm, camera) {
    var countNodesInHierarchy = function countNodesInHierarchyFn(root) {
        var count = 1;
        var children = root.children;
        if (children) {
            var numChildren = children.length;
            for (var c = 0; c < numChildren; c += 1) {
                count += countNodesInHierarchy(children[c]);
            }
        }

        return count;
    };

    var drawNodeHierarchy = function drawNodeHierarchyFn(root, writer, md) {
        var children = root.children;
        if (children) {
            var numChildren = children.length;
            for (var c = 0; c < numChildren; c += 1) {
                var child = children[c];

                var start = md.m43Pos(root.world);
                var end = md.m43Pos(child.world);

                writer(start, 0, 0, 0);
                writer(end, 0, 0, 0);

                drawNodeHierarchy(child, writer, md);
            }
        }
    };

    if (!this.rootNodes) {
        return;
    }
    var numNodes = 0;
    var numRoots = this.rootNodes.length;
    for (var n = 0; n < numRoots; n += 1) {
        numNodes += countNodesInHierarchy(this.rootNodes[n]);
    }

    var numVertices = 2 * numNodes;
    if (!numVertices) {
        return;
    }

    var shader = sm.load("shaders/debug.cgfx");
    var technique = shader.getTechnique("debug_lines");
    if (!technique) {
        return;
    }

    gd.setTechnique(technique);

    var techniqueParameters = this.debugLinesTechniqueParameters;
    if (!techniqueParameters) {
        techniqueParameters = gd.createTechniqueParameters({
            worldViewProjection: camera.viewProjectionMatrix
        });
        this.debugLinesTechniqueParameters = techniqueParameters;
    } else {
        techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
    }

    gd.setTechniqueParameters(techniqueParameters);

    var sem = this.getDebugSemanticsPosCol();
    var writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3, gd.VERTEXFORMAT_FLOAT3], sem);
    if (writer) {
        var md = this.md;
        for (n = 0; n < numRoots; n += 1) {
            var nodeRoot = this.rootNodes[n];

            drawNodeHierarchy(nodeRoot, writer, md);
        }

        gd.endDraw(writer);
    }
};

Scene.prototype.createGeoSphere = function scenecreateGeoSphereFn(radius, recursionLevel) {
    var positions = [];
    var indices = [];
    var cache = {};

    // Golden ratio
    var t = (1.0 + Math.sqrt(5.0)) / 2.0;

    // Default recursion level of 3
    recursionLevel = (!recursionLevel) ? 3 : recursionLevel;

    // add vertex to mesh, fix position to be on unit sphere then scale up to required radius
    // return index
    function addVertex(p0, p1, p2) {
        var length = Math.sqrt(p0 * p0 + p1 * p1 + p2 * p2);
        var scale = radius / length;
        positions[positions.length] = p0 * scale;
        positions[positions.length] = p1 * scale;
        positions[positions.length] = p2 * scale;
        return (positions.length / 3) - 1;
    }

    // return index of point in the middle of p1 and p2
    function getMiddlePoint(p1, p2) {
        // first check if we have it already
        var firstIsSmaller = p1 < p2;
        var k1 = firstIsSmaller ? p1 : p2;
        var k2 = firstIsSmaller ? p2 : p1;
        var key = k1.toString() + k2.toString() + (k1 + k2);

        if (cache[key]) {
            return cache[key];
        }

        // not in cache, calculate it - take in to account positions are stored
        // as a single array
        p1 = p1 * 3;
        p2 = p2 * 3;
        var i = addVertex((positions[p1] + positions[p2]) * 0.5, (positions[p1 + 1] + positions[p2 + 1]) * 0.5, (positions[p1 + 2] + positions[p2 + 2]) * 0.5);

        // store it, return index
        cache[key] = i;
        return i;
    }

    // create 12 vertices of an icosahedron - default unit parameters
    addVertex(-1, t, 0);
    addVertex(1, t, 0);
    addVertex(-1, -t, 0);
    addVertex(1, -t, 0);

    addVertex(0, -1, t);
    addVertex(0, 1, t);
    addVertex(0, -1, -t);
    addVertex(0, 1, -t);

    addVertex(t, 0, -1);
    addVertex(t, 0, 1);
    addVertex(-t, 0, -1);
    addVertex(-t, 0, 1);

    // create 20 triangles of the icosahedron
    indices = [
        0,
        11,
        5,
        0,
        5,
        1,
        0,
        1,
        7,
        0,
        7,
        10,
        0,
        10,
        11,
        1,
        5,
        9,
        5,
        11,
        4,
        11,
        10,
        2,
        10,
        7,
        6,
        7,
        1,
        8,
        3,
        9,
        4,
        3,
        4,
        2,
        3,
        2,
        6,
        3,
        6,
        8,
        3,
        8,
        9,
        4,
        9,
        5,
        2,
        4,
        11,
        6,
        2,
        10,
        8,
        6,
        7,
        9,
        8,
        1
    ];

    for (var i = 0; i < recursionLevel; i += 1) {
        var newindices = [];
        for (var j = 0; j < indices.length; j += 3) {
            // Current triangle
            var a = indices[j];
            var b = indices[j + 1];
            var c = indices[j + 2];

            // replace triangle by 4 triangles
            var d = getMiddlePoint(a, b);
            var e = getMiddlePoint(b, c);
            var f = getMiddlePoint(c, a);

            newindices[newindices.length] = a;
            newindices[newindices.length] = d;
            newindices[newindices.length] = f;

            newindices[newindices.length] = b;
            newindices[newindices.length] = e;
            newindices[newindices.length] = d;

            newindices[newindices.length] = c;
            newindices[newindices.length] = f;
            newindices[newindices.length] = e;

            newindices[newindices.length] = d;
            newindices[newindices.length] = e;
            newindices[newindices.length] = f;
        }
        indices = newindices;
    }

    return {
        indices: indices,
        vertices: positions,
        minExtent: [-radius, -radius, -radius],
        maxExtent: [radius, radius, radius]
    };
};

Scene.prototype.createCylinder = function sceneCreateCylinderFn(radius1, radius2, len, capEnds, tesselation) {
    var positions = [];
    var indices = [];
    var height = len / 2;

    // Default tesselation value of 10
    tesselation = (!tesselation) ? 10 : tesselation;

    var recTesselation = 1 / tesselation;
    var angleStep = (Math.PI * 2.0) * recTesselation;
    var angleStepHalf = angleStep * 0.5;

    var x = -height;
    var y = -height;
    var z = -height;

    for (var i = 0; i <= tesselation; i += 1) {
        var angle = angleStep * i;
        var cos = Math.cos(angle);
        var sin = Math.sin(angle);
        var coshalf = Math.cos(angle + angleStepHalf);
        var sinhalf = Math.sin(angle + angleStepHalf);

        x = radius1 * cos;
        y = -height;
        z = radius1 * sin;
        positions[positions.length] = x;
        positions[positions.length] = y;
        positions[positions.length] = z;

        x = radius2 * cos;
        y = height;
        z = radius2 * sin;
        positions[positions.length] = x;
        positions[positions.length] = y;
        positions[positions.length] = z;

        x = radius1 * coshalf;
        y = -height;
        z = radius1 * sinhalf;
        positions[positions.length] = x;
        positions[positions.length] = y;
        positions[positions.length] = z;

        x = radius2 * coshalf;
        y = height;
        z = radius2 * sinhalf;
        positions[positions.length] = x;
        positions[positions.length] = y;
        positions[positions.length] = z;
    }

    // Indices for the main hull part
    var nonWrappedSize = tesselation * 4;
    for (i = 0; i !== nonWrappedSize; i += 2) {
        indices[indices.length] = i + 2;
        indices[indices.length] = i;
        indices[indices.length] = i + 1;

        indices[indices.length] = i + 2;
        indices[indices.length] = i + 1;
        indices[indices.length] = i + 3;
    }

    // Two closing quads between end and start
    indices[indices.length] = 0;
    indices[indices.length] = i;
    indices[indices.length] = i + 1;

    indices[indices.length] = 0;
    indices[indices.length] = i + 1;
    indices[indices.length] = 1;

    if (capEnds) {
        var index = 0;

        if (radius1 !== 0) {
            x = 0.0;
            y = -height;
            z = 0.0;

            positions[positions.length] = x;
            positions[positions.length] = y;
            positions[positions.length] = z;

            index = (positions.length / 3) - 1;

            for (i = 0; i !== nonWrappedSize; i += 2) {
                indices[indices.length] = index;
                indices[indices.length] = i;
                indices[indices.length] = i + 2;
            }

            indices[indices.length] = index;
            indices[indices.length] = i;
            indices[indices.length] = 0;
        }

        if (radius2 !== 0) {
            y = height;
            z = 0.0;

            positions[positions.length] = x;
            positions[positions.length] = y;
            positions[positions.length] = z;

            index = (positions.length / 3) - 1;

            for (i = 0; i !== nonWrappedSize; i += 2) {
                indices[indices.length] = i + 1;
                indices[indices.length] = index;
                indices[indices.length] = i + 3;
            }

            indices[indices.length] = i + 1;
            indices[indices.length] = index;
            indices[indices.length] = 1;
        }
    }

    var radius = Math.max(radius1, radius2);

    return {
        indices: indices,
        vertices: positions,
        minExtent: [-radius, -height, -radius],
        maxExtent: [radius, height, radius]
    };
};

Scene.prototype.createRoundedPrimitive = function sceneCreateRoundedPrimitiveFn(mSizeX, mSizeY, mSizeZ, radius, mChamferNumSeg) {
    // radius = mChamferSize
    var md = this.md;

    // These can be used to set multiple segments instead of one long segment.
    // Must be scaled to the relevant size.
    // Eg. mNumSegX = 3 means mSizeX must be set to three as well otherwise the corners will be wrong
    var mNumSegX = 1;
    var mNumSegY = 1;
    var mNumSegZ = 1;

    // Setup some default parameters.
    // Also never allow the size to ACTUALLY be 0
    // Set it to something really small otherwise the planes won't render
    mSizeX = (mSizeX === 0) ? 0.0001 : mSizeX;
    mSizeY = (mSizeY === 0) ? 0.0001 : mSizeY;
    mSizeZ = (mSizeZ === 0) ? 0.0001 : mSizeZ;
    mChamferNumSeg = (!mChamferNumSeg) ? 8 : mChamferNumSeg;

    var offset = 0;
    var positions = [];
    var indices = [];
    var pi = Math.PI;

    // Cache mathDevice functions
    var v3Add4 = md.v3Add4;
    var v3Add3 = md.v3Add3;
    var v3ScalarMul = md.v3ScalarMul;

    function addCorner(isXPositive, isYPositive, isZPositive) {
        offset = (positions.length / 3);

        var offsetPosition = [
            (isXPositive ? 1 : -1) * 0.5 * mSizeX,
            (isYPositive ? 1 : -1) * 0.5 * mSizeY,
            (isZPositive ? 1 : -1) * 0.5 * mSizeZ
        ];

        var deltaRingAngle = ((pi / 2) / mChamferNumSeg);
        var offsetRingAngle = isYPositive ? 0 : (pi / 2);
        var offsetSegAngle;

        if (isXPositive && isZPositive) {
            offsetSegAngle = 0;
        }
        if (!isXPositive && isZPositive) {
            offsetSegAngle = 1.5 * pi;
        }
        if (isXPositive && !isZPositive) {
            offsetSegAngle = pi / 2;
        }
        if (!isXPositive && !isZPositive) {
            offsetSegAngle = pi;
        }

        for (var ring = 0; ring <= mChamferNumSeg; ring += 1) {
            var ringAngle = ring * deltaRingAngle + offsetRingAngle;
            var r0 = radius * Math.sin(ringAngle);
            var y0 = radius * Math.cos(ringAngle);

            for (var seg = 0; seg <= mChamferNumSeg; seg += 1) {
                var segAngle = seg * deltaRingAngle + offsetSegAngle;
                var x0 = r0 * Math.sin(segAngle);
                var z0 = r0 * Math.cos(segAngle);

                // Add one vertex to the strip which makes up the sphere
                positions[positions.length] = x0 + offsetPosition[0];
                positions[positions.length] = y0 + offsetPosition[1];
                positions[positions.length] = z0 + offsetPosition[2];

                if ((ring !== mChamferNumSeg) && (seg !== mChamferNumSeg)) {
                    // Each vertex (except the last) has six indices pointing to it
                    indices[indices.length] = offset + mChamferNumSeg + 2;
                    indices[indices.length] = offset;
                    indices[indices.length] = offset + mChamferNumSeg + 1;
                    indices[indices.length] = offset + mChamferNumSeg + 2;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset;
                }
                offset += 1;
            }
        }
    }

    // xPos,yPos,zPos : 1 => positive
    //                 -1 => negative
    //                  0 => undefined
    function addEdge(xPos, yPos, zPos) {
        var unitX = md.v3BuildXAxis(unitX);
        var unitY = md.v3BuildYAxis(unitY);
        var unitZ = md.v3BuildZAxis(unitZ);
        var ver;

        var tempx = v3ScalarMul.call(md, unitX, 0.5 * xPos * mSizeX);
        var tempy = v3ScalarMul.call(md, unitY, 0.5 * yPos * mSizeY);
        var tempz = v3ScalarMul.call(md, unitZ, 0.5 * zPos * mSizeZ);
        var centerPosition = v3Add3.call(md, tempx, tempy, tempz);

        tempx = v3ScalarMul.call(md, unitX, 1.0 - Math.abs(xPos));
        tempy = v3ScalarMul.call(md, unitY, 1.0 - Math.abs(yPos));
        tempz = v3ScalarMul.call(md, unitZ, 1.0 - Math.abs(zPos));
        var vy0 = v3Add3.call(md, tempx, tempy, tempz);
        var vx0 = md.v3Build(vy0[1], vy0[2], vy0[0]);
        var vz0 = md.v3Build(vy0[2], vy0[0], vy0[1]);

        offset = (positions.length / 3);

        if (md.v3Dot(vx0, centerPosition) < 0) {
            vx0 = md.v3Neg(vx0, vx0);
        }
        if (md.v3Dot(vz0, centerPosition) < 0) {
            vz0 = md.v3Neg(vz0, vz0);
        }

        var vxvy = md.v3Cross(vx0, vy0);
        if (md.v3Dot(vxvy, vz0) < 0) {
            vy0 = md.v3Neg(vy0, vy0);
        }

        var height = (1 - Math.abs(xPos)) * mSizeX + (1 - Math.abs(yPos)) * mSizeY + (1 - Math.abs(zPos)) * mSizeZ;

        var offsetPosition = md.v3Sub(centerPosition, md.v3ScalarMul(vy0, 0.5 * height));
        var numSegHeight = 1;

        var deltaAngle = ((Math.PI / 2) / mChamferNumSeg);
        var deltaHeight = height / numSegHeight;

        if (xPos === 0) {
            numSegHeight = mNumSegX;
        } else if (yPos === 0) {
            numSegHeight = mNumSegY;
        } else if (zPos === 0) {
            numSegHeight = mNumSegZ;
        }

        for (var i = 0; i <= numSegHeight; i += 1) {
            for (var j = 0; j <= mChamferNumSeg; j += 1) {
                var x0 = radius * Math.cos(j * deltaAngle);
                var z0 = radius * Math.sin(j * deltaAngle);

                // (x0 * vx0) + (i * deltaHeight * vy0) + (z0 * vz0) + offsetPosition
                v3ScalarMul.call(md, vx0, x0, tempx);
                v3ScalarMul.call(md, vy0, i * deltaHeight, tempy);
                v3ScalarMul.call(md, vz0, z0, tempz);
                ver = v3Add4.call(md, tempx, tempy, tempz, offsetPosition, ver);

                positions[positions.length] = ver[0];
                positions[positions.length] = ver[1];
                positions[positions.length] = ver[2];

                if (i !== numSegHeight && j !== mChamferNumSeg) {
                    indices[indices.length] = offset + mChamferNumSeg + 2;
                    indices[indices.length] = offset;
                    indices[indices.length] = offset + mChamferNumSeg + 1;
                    indices[indices.length] = offset + mChamferNumSeg + 2;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset;
                }
                offset += 1;
            }
        }
    }

    function generatePlane(numSegX, numSegY, sizeX, sizeY, normal, position) {
        offset = (positions.length / 3);

        // Generate a perpendicular to the normal
        // There are infinitely many of these, we have just chosen to build one with the X-axis
        // If the normal is aligned to the X-axis then calculate again with the Y-axis
        var vX = md.v3Cross(normal, md.v3BuildXAxis());
        if (md.v3LengthSq(vX) < 0.0000001) {
            md.v3Cross(normal, md.v3BuildYAxis(), vX);
        }
        md.v3Normalize(vX, vX);

        var vY = md.v3Cross(normal, vX);
        var delta1 = v3ScalarMul.call(md, vX, sizeX / numSegX);
        var delta2 = v3ScalarMul.call(md, vY, sizeY / numSegY);

        // Build one corner of the square
        var orig = md.v3Sub(v3ScalarMul.call(md, vX, -0.5 * sizeX), v3ScalarMul.call(md, vY, 0.5 * sizeY));

        for (var i1 = 0; i1 <= numSegX; i1 += 1) {
            for (var i2 = 0; i2 <= numSegY; i2 += 1) {
                var pos = v3Add4.call(md, orig, v3ScalarMul.call(md, delta1, i1), v3ScalarMul.call(md, delta2, i2), position, pos);
                positions[positions.length] = pos[0];
                positions[positions.length] = pos[1];
                positions[positions.length] = pos[2];
            }
        }

        var reverse = false;
        var d1d2 = md.v3Cross(delta1, delta2);
        if (md.v3Dot(d1d2, normal) > 0) {
            reverse = true;
        }
        for (var n1 = 0; n1 < numSegX; n1 += 1) {
            for (var n2 = 0; n2 < numSegY; n2 += 1) {
                if (reverse) {
                    indices[indices.length] = offset;
                    indices[indices.length] = offset + numSegY + 1;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset + numSegY + 1;
                    indices[indices.length] = offset + numSegY + 1 + 1;
                } else {
                    indices[indices.length] = offset;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset + numSegY + 1;
                    indices[indices.length] = offset + 1;
                    indices[indices.length] = offset + numSegY + 1 + 1;
                    indices[indices.length] = offset + numSegY + 1;
                }
                offset += 1;
            }
            offset += 1;
        }
    }

    var hX = (0.5 * mSizeX + radius);
    var hY = (0.5 * mSizeY + radius);
    var hZ = (0.5 * mSizeZ + radius);

    var planeNorm = md.v3Neg(md.v3BuildZAxis(), planeNorm);
    var planePos = v3ScalarMul.call(md, planeNorm, hZ);

    // Generate the pseudo-box shape
    generatePlane(mNumSegY, mNumSegX, mSizeY, mSizeX, planeNorm, planePos);

    planeNorm = md.v3BuildZAxis(planeNorm);
    v3ScalarMul.call(md, planeNorm, hZ, planePos);
    generatePlane(mNumSegY, mNumSegX, mSizeY, mSizeX, planeNorm, planePos);

    md.v3Neg(md.v3BuildYAxis(planeNorm), planeNorm);
    v3ScalarMul.call(md, planeNorm, hY, planePos);
    generatePlane(mNumSegZ, mNumSegX, mSizeZ, mSizeX, planeNorm, planePos);

    md.v3BuildYAxis(planeNorm);
    v3ScalarMul.call(md, planeNorm, hY, planePos);
    generatePlane(mNumSegZ, mNumSegX, mSizeZ, mSizeX, planeNorm, planePos);

    md.v3Neg(md.v3BuildXAxis(planeNorm), planeNorm);
    v3ScalarMul.call(md, planeNorm, hX, planePos);
    generatePlane(mNumSegZ, mNumSegY, mSizeZ, mSizeY, planeNorm, planePos);

    md.v3BuildXAxis(planeNorm);
    v3ScalarMul.call(md, planeNorm, hX, planePos);
    generatePlane(mNumSegZ, mNumSegY, mSizeZ, mSizeY, planeNorm, planePos);

    // Generate the corners
    addCorner(true, true, true);
    addCorner(true, true, false);
    addCorner(true, false, true);
    addCorner(true, false, false);
    addCorner(false, true, true);
    addCorner(false, true, false);
    addCorner(false, false, true);
    addCorner(false, false, false);

    // Generate the edges
    addEdge(-1, -1, 0);
    addEdge(-1, 1, 0);
    addEdge(1, -1, 0);
    addEdge(1, 1, 0);
    addEdge(-1, 0, -1);
    addEdge(-1, 0, 1);
    addEdge(1, 0, -1);
    addEdge(1, 0, 1);
    addEdge(0, -1, -1);
    addEdge(0, -1, 1);
    addEdge(0, 1, -1);
    addEdge(0, 1, 1);

    return {
        indices: indices,
        vertices: positions,
        minExtent: [-hX, -hY, -hZ],
        maxExtent: [hX, hY, hZ]
    };
};

Scene.prototype.createBox = function sceneCreateBoxFn(halfExtents) {
    var xHalfExtent = halfExtents[0];
    var yHalfExtent = halfExtents[1];
    var zHalfExtent = halfExtents[2];

    var positions = [
        -xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        -zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        -zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        -xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        -xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        -zHalfExtent,
        xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        -yHalfExtent,
        -zHalfExtent,
        -xHalfExtent,
        -yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        zHalfExtent,
        -xHalfExtent,
        yHalfExtent,
        -zHalfExtent
    ];

    var indices = [
        2,
        0,
        1,
        3,
        0,
        2,
        6,
        4,
        5,
        7,
        4,
        6,
        10,
        8,
        9,
        11,
        8,
        10,
        14,
        12,
        13,
        15,
        12,
        14,
        18,
        16,
        17,
        19,
        16,
        18,
        22,
        20,
        21,
        23,
        20,
        22
    ];

    return {
        indices: indices,
        vertices: positions,
        minExtent: [-xHalfExtent, -yHalfExtent, -zHalfExtent],
        maxExtent: [xHalfExtent, yHalfExtent, zHalfExtent]
    };
};

Scene.prototype.createConvexHull = function sceneCreateConvexHull(dw, body, numRays) {
    if (TurbulenzEngine.canvas) {
        // Special case for WebGL.
        // ConvexHull posesses a TriangleArray with vertices/indicies
        //   to render a triangle mesh for debug view instead of
        //   the much slower and less helpful ray casted positioned squares.
        return body.shape._private.triangleArray;
    }

    var positions = [];
    var indices = [];
    var md = this.md;
    var offset = 0;

    var transform = body.transform;
    var pos = md.m43Pos(transform);
    var halfextents = body.shape.halfExtents;
    var sqrtNumRays = Math.ceil(Math.sqrt(numRays));

    var biggestHalfExtent = halfextents[0];
    biggestHalfExtent = (halfextents[1] > biggestHalfExtent) ? halfextents[1] : biggestHalfExtent;
    biggestHalfExtent = (halfextents[1] > biggestHalfExtent) ? halfextents[2] : biggestHalfExtent;
    var scale = (biggestHalfExtent / numRays) * (numRays / 5);

    function addSquare(position, normal) {
        // Cache mathDevice functions
        var v3Add4 = md.v3Add4;
        var v3ScalarMul = md.v3ScalarMul;

        offset = (positions.length / 3);

        // Generate a perpendicular to the normal
        // There are infinitely many of these, we have just chosen to build one with the X-axis
        // If the normal is aligned to the X-axis then calculate again with the Y-axis
        var vX = md.v3Cross(normal, md.v3BuildXAxis());
        if (md.v3LengthSq(vX) < 0.0000001) {
            md.v3Cross(normal, md.v3BuildYAxis(), vX);
        }
        md.v3Normalize(vX, vX);

        var vY = md.v3Cross(normal, vX);
        var delta1 = md.v3ScalarMul(vX, scale);
        var delta2 = md.v3ScalarMul(vY, scale);

        // Build one corner of the square
        var orig = md.v3Sub(md.v3ScalarMul(vX, -0.5 * scale), md.v3ScalarMul(vY, 0.5 * scale));

        for (var i1 = 0; i1 < 2; i1 += 1) {
            for (var i2 = 0; i2 < 2; i2 += 1) {
                var pos = v3Add4.call(md, orig, v3ScalarMul.call(md, delta1, i1), v3ScalarMul.call(md, delta2, i2), position, pos);
                positions[positions.length] = pos[0];
                positions[positions.length] = pos[1];
                positions[positions.length] = pos[2];
            }
        }

        var reverse = false;
        var d1d2 = md.v3Cross(delta1, delta2);
        if (md.v3Dot(d1d2, normal) > 0) {
            reverse = true;
        }
        if (reverse) {
            indices[indices.length] = offset;
            indices[indices.length] = offset + 2;
            indices[indices.length] = offset + 1;
            indices[indices.length] = offset + 1;
            indices[indices.length] = offset + 2;
            indices[indices.length] = offset + 3;
        } else {
            indices[indices.length] = offset;
            indices[indices.length] = offset + 1;
            indices[indices.length] = offset + 2;
            indices[indices.length] = offset + 1;
            indices[indices.length] = offset + 3;
            indices[indices.length] = offset + 2;
        }
    }

    function rayCastFromPlane(index, neg) {
        // Cache plugin functions
        var v3Add = md.v3Add;
        var v3Add3 = md.v3Add3;
        var rayTest = dw.rayTest;
        var v3Sub = md.v3Sub;
        var m43InverseOrthonormal = md.m43InverseOrthonormal;
        var m43TransformPoint = md.m43TransformPoint;
        var m43TransformVector = md.m43TransformVector;

        var mask = [];
        mask[0] = md.v3BuildXAxis();
        mask[1] = md.v3BuildYAxis();
        mask[2] = md.v3BuildZAxis();

        var min1 = v3Sub.call(md, md.v3Mul(pos, mask[(index + 1) % 3]), md.v3Mul(halfextents, mask[(index + 1) % 3]));
        var min2 = v3Sub.call(md, md.v3Mul(pos, mask[(index + 2) % 3]), md.v3Mul(halfextents, mask[(index + 2) % 3]));
        var depthmax = v3Sub.call(md, md.v3Mul(pos, mask[index]), md.v3Mul(halfextents, mask[index]));
        var depthmin = v3Add.call(md, md.v3Mul(pos, mask[index]), md.v3Mul(halfextents, mask[index]));
        if (neg) {
            var temp = depthmax;
            depthmax = depthmin;
            depthmin = temp;
        }

        // ((halfextents * mask) * 2) / sqrtNumRays
        var step1 = md.v3ScalarMul(md.v3ScalarMul(md.v3Mul(halfextents, mask[(index + 1) % 3]), 2), 1 / sqrtNumRays);
        var step2 = md.v3ScalarMul(md.v3ScalarMul(md.v3Mul(halfextents, mask[(index + 2) % 3]), 2), 1 / sqrtNumRays);

        for (var i = 0; i < sqrtNumRays; i += 1) {
            for (var k = 0; k < sqrtNumRays; k += 1) {
                var u = v3Add.call(md, min1, md.v3ScalarMul(step1, i));
                var v = v3Add.call(md, min2, md.v3ScalarMul(step2, k));
                var from = v3Add3.call(md, u, v, depthmin, from);
                var to = v3Add3.call(md, u, v, depthmax, to);
                var rayHit = rayTest.call(dw, {
                    from: from,
                    to: to
                });
                if (rayHit && (rayHit.body === body || rayHit.collisionObject === body)) {
                    var hitPoint = rayHit.hitPoint;
                    var normal = rayHit.hitNormal;
                    var inv = m43InverseOrthonormal.call(md, transform);
                    m43TransformPoint.call(md, inv, hitPoint, hitPoint);
                    m43TransformVector.call(md, inv, normal, normal);
                    addSquare(hitPoint, normal);
                }
            }
        }
    }

    rayCastFromPlane(0, false);
    rayCastFromPlane(0, true);
    rayCastFromPlane(1, false);
    rayCastFromPlane(1, true);
    rayCastFromPlane(2, false);
    rayCastFromPlane(2, true);

    return {
        indices: indices,
        vertices: positions
    };
};

//
// drawPhysicsNodes
//
Scene.prototype.drawPhysicsNodes = function sceneDrawPhysicsNodesFn(gd, sm, camera, physicsManager) {
    var shader = sm.load("shaders/debug.cgfx");
    var technique = shader.getTechnique("debug_lines");
    if (!technique) {
        return;
    }

    var physicsNodes = physicsManager.physicsNodes;
    var previousFrameIndex = (this.frameIndex - 1);
    var isInsidePlanesAABB = this.isInsidePlanesAABB;
    var frustumPlanes = this.frustumPlanes;
    var n, physicsNode, target;
    var extents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(6) : new Array(6));
    var numNodes = physicsNodes.length;
    var visiblePhysicsNodes = [];
    for (n = 0; n < numNodes; n += 1) {
        physicsNode = physicsNodes[n];
        target = physicsNode.target;
        if (target.frameVisible >= previousFrameIndex) {
            visiblePhysicsNodes[visiblePhysicsNodes.length] = physicsNode;
        } else {
            physicsNode.body.calculateExtents(extents);
            if (isInsidePlanesAABB(extents, frustumPlanes)) {
                visiblePhysicsNodes[visiblePhysicsNodes.length] = physicsNode;
            }
        }
    }

    numNodes = visiblePhysicsNodes.length;
    if (!numNodes) {
        return;
    }

    gd.setTechnique(technique);

    var techniqueParameters = this.debugLinesTechniqueParameters;
    if (!techniqueParameters) {
        techniqueParameters = gd.createTechniqueParameters({
            worldViewProjection: camera.viewProjectionMatrix
        });
        this.debugLinesTechniqueParameters = techniqueParameters;
    } else {
        techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
    }

    gd.setTechniqueParameters(techniqueParameters);

    var sem = this.getDebugSemanticsPosCol();
    var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
    var writer = gd.beginDraw(gd.PRIMITIVE_LINES, (24 * numNodes), [vformatFloat3, vformatFloat3], sem);
    if (writer) {
        var transform = physicsManager.mathsDevice.m43BuildIdentity();
        for (n = 0; n < numNodes; n += 1) {
            physicsNode = visiblePhysicsNodes[n];
            target = physicsNode.target;
            var scale = (target.disabled ? 0.2 : 1.0);
            var r, g, b;
            if (physicsNode.kinematic) {
                r = 0;
                g = 0;
                b = scale;
            } else if (physicsNode.dynamic) {
                r = 0;
                g = scale;
                b = 0;
            } else {
                r = scale;
                g = 0;
                b = 0;
            }
            var body = physicsNode.body;
            body.calculateTransform(transform);
            this.writeRotatedBox(writer, transform, body.shape.halfExtents, r, g, b);
        }

        gd.endDraw(writer);
    }
};

//
// drawPhysicsGeometry
//
Scene.prototype.drawPhysicsGeometry = function sceneDrawPhysicsGeometryFn(gd, sm, camera, physicsManager) {
    var shader = sm.load("shaders/debug.cgfx");
    var technique = shader.getTechnique("physics_debug");
    if (!technique) {
        return;
    }

    var md = this.md;

    // Cache vertex formats
    var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
    var vformatFloat4 = gd.VERTEXFORMAT_FLOAT4;
    var attributes = [vformatFloat4, vformatFloat3, vformatFloat3];
    var numAttributeComponents = 10;

    var physicsNodes = physicsManager.physicsNodes;
    var previousFrameIndex = (this.frameIndex - 1);
    var isInsidePlanesAABB = this.isInsidePlanesAABB;
    var frustumPlanes = this.frustumPlanes;
    var n, physicsNode, target, triangleArray, visible, positions, i, indices, numIndices;
    var extents = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(6) : new Array(6));
    var numNodes = physicsNodes.length;
    var visiblePhysicsNodes = [];
    var triangleArrayParams;
    var pd = physicsManager.physicsDevice;
    var dw = physicsManager.dynamicsWorld;
    var shape;
    for (n = 0; n < numNodes; n += 1) {
        physicsNode = physicsNodes[n];

        visible = false;
        target = physicsNode.target;
        if (target.frameVisible >= previousFrameIndex) {
            visible = true;
        } else {
            physicsNode.body.calculateExtents(extents);
            if (isInsidePlanesAABB(extents, frustumPlanes)) {
                visible = true;
            }
        }

        if (visible) {
            visiblePhysicsNodes[visiblePhysicsNodes.length] = physicsNode;

            triangleArray = physicsNode.triangleArray;
            if (!triangleArray) {
                shape = physicsNode.body.shape;
                var type = shape.type;
                var halfExtents = shape.halfExtents;

                if (type === "SPHERE") {
                    triangleArrayParams = this.createGeoSphere(shape.radius);
                } else if (type === "CYLINDER") {
                    triangleArrayParams = this.createCylinder(halfExtents[0], halfExtents[2], halfExtents[1] * 2, true);
                } else if (type === "CONE") {
                    triangleArrayParams = this.createCylinder(halfExtents[0], 0, halfExtents[1] * 2, true);
                } else if (type === "CAPSULE") {
                    var height = (halfExtents[1] - halfExtents[0]) * 2;
                    triangleArrayParams = this.createRoundedPrimitive(0, height, 0, halfExtents[0]);
                } else if (type === "BOX") {
                    triangleArrayParams = this.createBox(halfExtents);
                } else if (type === "CONVEX_HULL") {
                    triangleArrayParams = this.createConvexHull(dw, physicsNode.body, 50);
                } else if (type === "TRIANGLE_MESH") {
                    triangleArrayParams = shape.triangleArray;
                }

                if (triangleArrayParams && triangleArrayParams.vertices.length > 0) {
                    if (triangleArrayParams.triangles) {
                        triangleArray = triangleArrayParams;
                    } else {
                        triangleArray = pd.createTriangleArray(triangleArrayParams);
                    }
                    physicsNode.triangleArray = triangleArray;
                } else {
                    visiblePhysicsNodes.pop();
                    continue;
                }
            }

            positions = physicsNode.positions;
            if (!positions && triangleArray) {
                var vertices = triangleArray.vertices;

                if (!TurbulenzEngine.canvas) {
                    var numVertexComponents = vertices.length;
                    positions = [];
                    positions.length = numVertexComponents;
                    for (i = 0; i < numVertexComponents; i += 1) {
                        positions[i] = vertices[i];
                    }
                } else {
                    positions = vertices;
                }

                physicsNode.positions = positions;
                physicsNode.indices = triangleArray.indices;
            }

            if (!physicsNode.wireframeBuffer && positions) {
                indices = physicsNode.indices;
                numIndices = indices.length;

                var vData = (this.float32ArrayConstructor ? new this.float32ArrayConstructor(numIndices * numAttributeComponents) : new Array(numIndices * numAttributeComponents));
                var j;
                var dstIndex = 0;
                var vdIndex0, vdValue0x, vdValue0y, vdValue0z, vdIndex1, vdValue1x, vdValue1y, vdValue1z, vdIndex2, vdValue2x, vdValue2y, vdValue2z;

                var vertexBuffer = gd.createVertexBuffer({
                    numVertices: numIndices,
                    attributes: attributes
                });

                for (j = 0; j < numIndices; j += 3) {
                    vdIndex0 = 3 * indices[j];
                    vdIndex1 = 3 * indices[j + 1];
                    vdIndex2 = 3 * indices[j + 2];

                    //Vertex 0
                    vdValue0x = positions[vdIndex0];
                    vdValue0y = positions[vdIndex0 + 1];
                    vdValue0z = positions[vdIndex0 + 2];
                    vData[dstIndex] = vdValue0x;
                    vData[dstIndex + 1] = vdValue0y;
                    vData[dstIndex + 2] = vdValue0z;
                    vData[dstIndex + 3] = 0;

                    //Vertex 1 passed as attribute of Vertex 0
                    vdValue1x = positions[vdIndex1];
                    vdValue1y = positions[vdIndex1 + 1];
                    vdValue1z = positions[vdIndex1 + 2];
                    vData[dstIndex + 4] = vdValue1x;
                    vData[dstIndex + 5] = vdValue1y;
                    vData[dstIndex + 6] = vdValue1z;

                    //Vertex 2 passed as attribute of Vertex 0
                    vdValue2x = positions[vdIndex2];
                    vdValue2y = positions[vdIndex2 + 1];
                    vdValue2z = positions[vdIndex2 + 2];
                    vData[dstIndex + 7] = vdValue2x;
                    vData[dstIndex + 8] = vdValue2y;
                    vData[dstIndex + 9] = vdValue2z;

                    //Depending on whether skinned or not, increments accordingly
                    dstIndex += numAttributeComponents;

                    //Repeat for Vertex 1
                    vData[dstIndex] = vdValue1x;
                    vData[dstIndex + 1] = vdValue1y;
                    vData[dstIndex + 2] = vdValue1z;
                    vData[dstIndex + 3] = 1;
                    vData[dstIndex + 4] = vdValue0x;
                    vData[dstIndex + 5] = vdValue0y;
                    vData[dstIndex + 6] = vdValue0z;
                    vData[dstIndex + 7] = vdValue2x;
                    vData[dstIndex + 8] = vdValue2y;
                    vData[dstIndex + 9] = vdValue2z;
                    dstIndex += numAttributeComponents;

                    //Repeat for Vertex 2
                    vData[dstIndex] = vdValue2x;
                    vData[dstIndex + 1] = vdValue2y;
                    vData[dstIndex + 2] = vdValue2z;
                    vData[dstIndex + 3] = 2;
                    vData[dstIndex + 4] = vdValue0x;
                    vData[dstIndex + 5] = vdValue0y;
                    vData[dstIndex + 6] = vdValue0z;
                    vData[dstIndex + 7] = vdValue1x;
                    vData[dstIndex + 8] = vdValue1y;
                    vData[dstIndex + 9] = vdValue1z;
                    dstIndex += numAttributeComponents;
                }

                vertexBuffer.setData(vData);

                physicsNode.wireframeBuffer = vertexBuffer;
            }
        }
    }

    numNodes = visiblePhysicsNodes.length;
    if (!numNodes) {
        return;
    }

    // Cache math functions and vertex formats
    var v4Build = md.v4Build;
    var m43MulM44 = md.m43MulM44;

    // Set technique and shared parameters
    gd.setTechnique(technique);

    technique.windowScale = [gd.width / 2, gd.height / 2];
    technique.wireColor = v4Build.call(md, 0, 0.2, 0.6, 1);
    technique.alphaRef = 0;
    technique.alpha = 0.5;

    var fillColor, worldViewProjection;
    var transform = md.m43BuildIdentity();

    var wireframeSemantics = this.physicsWireframeSemantics;
    if (!wireframeSemantics) {
        this.physicsWireframeSemantics = wireframeSemantics = gd.createSemantics(['POSITION', 'TEXCOORD0', 'TEXCOORD1']);
    }

    for (n = 0; n < numNodes; n += 1) {
        physicsNode = visiblePhysicsNodes[n];
        target = physicsNode.target;
        var body = physicsNode.body;
        var scale = (target.disabled ? 0.2 : body.active ? 1.0 : 0.4);
        var tintR, tintG, tintB;
        if (physicsNode.kinematic) {
            tintR = 0;
            tintG = 0;
            tintB = scale;
        } else if (physicsNode.dynamic) {
            tintR = 0;
            tintG = scale;
            tintB = 0;
        } else {
            tintR = scale;
            tintG = 0;
            tintB = 0;
        }

        var r, g, b;
        if (body.type === "CHARACTER") {
            r = 0;
            g = 0;
            b = 0;
        } else {
            shape = body.shape;
            if (shape.type === "TRIANGLE_MESH") {
                r = 1;
                g = 0;
                b = 0;
            } else if (shape.type === "CONVEX_HULL") {
                r = 0;
                g = 0;
                b = 1;
            } else {
                r = 0;
                g = 1;
                b = 0;
            }
        }

        r = 0.5 * (r + tintR);
        g = 0.5 * (g + tintG);
        b = 0.5 * (b + tintB);

        fillColor = v4Build.call(md, r, g, b, 0, fillColor);
        body.calculateTransform(transform);
        worldViewProjection = m43MulM44.call(md, transform, camera.viewProjectionMatrix, worldViewProjection);

        technique.fillColor = fillColor;
        technique.worldViewProjection = worldViewProjection;

        var wireframeBuffer = physicsNode.wireframeBuffer;

        gd.setStream(wireframeBuffer, wireframeSemantics);

        gd.draw(gd.PRIMITIVE_TRIANGLES, wireframeBuffer.numVertices, 0);
    }
};

//
// drawVisibleRenderablesExtents
//
Scene.prototype.drawVisibleRenderablesExtents = function sceneDrawVisibleRenderablesExtentsFn(gd, sm, camera, drawDecals, drawTransparents) {
    var renderables = this.visibleRenderables;
    var numRenderables = renderables.length;
    if (numRenderables) {
        var n, renderable, meta;
        var renderablesExtents = [];
        var extents;
        for (n = 0; n < numRenderables; n += 1) {
            renderable = renderables[n];
            extents = renderable.getWorldExtents();
            if (extents) {
                meta = renderable.sharedMaterial.meta;
                if (meta.decal) {
                    if (drawDecals) {
                        renderablesExtents.push(extents);
                    }
                } else if (meta.transparent) {
                    if (drawTransparents) {
                        renderablesExtents.push(extents);
                    }
                } else {
                    if (!drawDecals && !drawTransparents) {
                        renderablesExtents.push(extents);
                    }
                }
            }
        }

        var numExtents = renderablesExtents.length;
        if (!numExtents) {
            return;
        }

        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines_constant");
        if (!technique) {
            return;
        }

        var r, g, b;
        if (drawDecals) {
            r = 1;
            g = 0;
            b = 0;
        } else if (drawTransparents) {
            r = 0;
            g = 0;
            b = 1;
        } else {
            r = 0;
            g = 1;
            b = 0;
        }

        gd.setTechnique(technique);

        var md = this.md;
        var techniqueParameters = this.debugLinesConstantTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix,
                constantColor: md.v4Build(r, g, b, 1.0)
            });
            this.debugLinesConstantTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
            techniqueParameters.constantColor = md.v4Build(r, g, b, 1.0, techniqueParameters.constantColor);
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPos();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, (24 * numExtents), [gd.VERTEXFORMAT_FLOAT3], sem);
        if (writer) {
            for (n = 0; n < numExtents; n += 1) {
                extents = renderablesExtents[n];
                var p0 = extents[0];
                var p1 = extents[1];
                var p2 = extents[2];
                var n0 = extents[3];
                var n1 = extents[4];
                var n2 = extents[5];

                writer(p0, p1, p2);
                writer(p0, p1, n2);

                writer(p0, p1, p2);
                writer(p0, n1, p2);

                writer(p0, p1, p2);
                writer(n0, p1, p2);

                writer(n0, n1, n2);
                writer(n0, n1, p2);

                writer(n0, n1, n2);
                writer(n0, p1, n2);

                writer(n0, n1, n2);
                writer(p0, n1, n2);

                writer(p0, n1, n2);
                writer(p0, n1, p2);

                writer(p0, n1, n2);
                writer(p0, p1, n2);

                writer(n0, n1, p2);
                writer(p0, n1, p2);

                writer(n0, n1, p2);
                writer(n0, p1, p2);

                writer(n0, p1, n2);
                writer(p0, p1, n2);

                writer(n0, p1, n2);
                writer(n0, p1, p2);
            }

            gd.endDraw(writer);

            writer = null;
        }
    }
};

//
// drawOpaqueNodesExtents
//
Scene.prototype.drawOpaqueNodesExtents = function drawOpaqueNodesExtentsFn(gd, sm, camera) {
    this.drawVisibleRenderablesExtents(gd, sm, camera, false, false);
};

//
// drawDecalNodesExtents
//
Scene.prototype.drawDecalNodesExtents = function drawDecalNodesExtentsFn(gd, sm, camera) {
    this.drawVisibleRenderablesExtents(gd, sm, camera, true, false);
};

//
// drawTransparentNodesExtents
//
Scene.prototype.drawTransparentNodesExtents = function drawTransparentNodesExtentsFn(gd, sm, camera) {
    this.drawVisibleRenderablesExtents(gd, sm, camera, false, true);
};

//
// drawStaticNodesTree
//
Scene.prototype.drawStaticNodesTree = function sceneDrawStaticNodesTreeFn(gd, sm, camera, drawLevel) {
    if (this.staticSpatialMap.getNodes) {
        this.drawNodesTree(this.staticSpatialMap, gd, sm, camera, drawLevel);
    } else if (this.staticSpatialMap.getCells) {
        this.drawCellsGrid(this.staticSpatialMap, gd, sm, camera);
    }
};

//
// drawDynamicNodesTree
//
Scene.prototype.drawDynamicNodesTree = function sceneDrawDynamicNodesTreeFn(gd, sm, camera, drawLevel) {
    if (this.dynamicSpatialMap.getNodes) {
        this.drawNodesTree(this.dynamicSpatialMap, gd, sm, camera, drawLevel);
    } else if (this.dynamicSpatialMap.getCells) {
        this.drawCellsGrid(this.dynamicSpatialMap, gd, sm, camera);
    }
};

//
// drawNodesTree
//
Scene.prototype.drawNodesTree = function sceneDrawNodesTreeFn(tree, gd, sm, camera, drawLevel) {
    function drawNodeFn(writer, nodes, idx, level) {
        var node = nodes[idx];

        if (level === 0) {
            var extents = node.extents;
            var p0 = extents[0];
            var p1 = extents[1];
            var p2 = extents[2];
            var n0 = extents[3];
            var n1 = extents[4];
            var n2 = extents[5];

            writer(p0, p1, p2);
            writer(p0, p1, n2);

            writer(p0, p1, p2);
            writer(p0, n1, p2);

            writer(p0, p1, p2);
            writer(n0, p1, p2);

            writer(n0, n1, n2);
            writer(n0, n1, p2);

            writer(n0, n1, n2);
            writer(n0, p1, n2);

            writer(n0, n1, n2);
            writer(p0, n1, n2);

            writer(p0, n1, n2);
            writer(p0, n1, p2);

            writer(p0, n1, n2);
            writer(p0, p1, n2);

            writer(n0, n1, p2);
            writer(p0, n1, p2);

            writer(n0, n1, p2);
            writer(n0, p1, p2);

            writer(n0, p1, n2);
            writer(p0, p1, n2);

            writer(n0, p1, n2);
            writer(n0, p1, p2);

            return (idx + node.escapeNodeOffset);
        } else {
            if (node.isLeaf()) {
                return (idx + 1);
            } else {
                var endIndex = (idx + node.escapeNodeOffset);
                level -= 1;
                idx += 1;
                do {
                    idx = drawNodeFn(writer, nodes, idx, level);
                } while(idx < endIndex);
                return idx;
            }
        }
    }

    var nodes = tree.getNodes();
    var numNodes = tree.getEndNodeIndex();
    if (numNodes) {
        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("debug_lines_constant");
        if (!technique) {
            return;
        }

        gd.setTechnique(technique);

        var md = this.md;
        var techniqueParameters = this.debugLinesConstantTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: null,
                constantColor: null
            });
            this.debugLinesConstantTechniqueParameters = techniqueParameters;
        }

        techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        techniqueParameters.constantColor = md.v4Build(1.0, 0.0, 0.0, 1.0);

        gd.setTechniqueParameters(techniqueParameters);

        var numVertices = 24 * md.truncate(Math.pow(2, drawLevel));

        var sem = this.getDebugSemanticsPos();
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, numVertices, [gd.VERTEXFORMAT_FLOAT3], sem);
        if (writer) {
            drawNodeFn(writer, nodes, 0, drawLevel);

            gd.endDraw(writer);

            writer = null;
        }
    }
};

//
// drawCellsGrid
//
Scene.prototype.drawCellsGrid = function sceneDrawCellsGridFn(grid, gd, sm, camera) {
    var shader = sm.load("shaders/debug.cgfx");
    var technique = shader.getTechnique("debug_lines");
    if (!technique) {
        return;
    }

    var cells = grid.getCells();
    var numCells = cells.length;

    var maxNodesPerCell = 0;
    var numUsedCells = 0;
    var n, cell, numNodes;
    for (n = 0; n < numCells; n += 1) {
        cell = cells[n];
        if (cell) {
            numNodes = cell.length;
            if (maxNodesPerCell < numNodes) {
                maxNodesPerCell = numNodes;
            }
            numUsedCells += 1;
        }
    }

    if (numUsedCells) {
        gd.setTechnique(technique);

        var techniqueParameters = this.debugLinesTechniqueParameters;
        if (!techniqueParameters) {
            techniqueParameters = gd.createTechniqueParameters({
                worldViewProjection: camera.viewProjectionMatrix
            });
            this.debugLinesTechniqueParameters = techniqueParameters;
        } else {
            techniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
        }

        gd.setTechniqueParameters(techniqueParameters);

        var sem = this.getDebugSemanticsPosCol();
        var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
        var writer = gd.beginDraw(gd.PRIMITIVE_LINES, 24 * numUsedCells, [vformatFloat3, vformatFloat3], sem);
        if (writer) {
            var cellSize = grid.getCellSize();
            var gridExtents = grid.getExtents();
            var minGridX = gridExtents[0];
            var minGridY = gridExtents[1];
            var minGridZ = gridExtents[2];
            var maxGridX = gridExtents[3];
            var maxGridY = gridExtents[4];
            var maxGridZ = gridExtents[5];

            var numCellsX = Math.ceil((maxGridX - minGridX) / cellSize);
            var numCellsZ = Math.ceil((maxGridZ - minGridZ) / cellSize);
            var cellExtents = gridExtents.slice(0);

            var writeBox = this.writeBox;
            var colorScale = (1.0 / maxNodesPerCell);

            var j, i, gb;
            cellExtents[2] = minGridZ;
            cellExtents[5] = (minGridZ + cellSize);
            n = 0;
            for (j = 0; j < numCellsZ; j += 1) {
                cellExtents[0] = minGridX;
                cellExtents[3] = (minGridX + cellSize);
                for (i = 0; i < numCellsX; i += 1) {
                    cell = cells[n];
                    n += 1;

                    if (cell) {
                        numNodes = cell.length;
                        gb = (1 === numNodes ? 0 : (numNodes * colorScale));
                        writeBox(writer, cellExtents, 1.0, gb, gb);
                    }

                    cellExtents[0] = cellExtents[3];
                    cellExtents[3] += cellSize;
                }

                cellExtents[2] = cellExtents[5];
                cellExtents[5] += cellSize;
            }

            gd.endDraw(writer);

            writer = null;
        }
    }
};

//
// updateNormals
//
Scene.prototype.updateNormals = function updateNormalsFn(gd, scale, drawNormals, normalMaterial, drawTangents, tangentMaterial, drawBinormals, binormalMaterial) {
    var halfExtents;
    var center;
    var node;
    var stride;

    var createNormalsGeometryInstance = function createNormalsGeometryInstanceFn(normalsNumVerts, material) {
        var normalsVertexBuffer = gd.createVertexBuffer({
            numVertices: normalsNumVerts,
            attributes: [gd.VERTEXFORMAT_FLOAT3],
            dynamic: true
        });

        var normalsGeometry = Geometry.create();

        normalsGeometry.halfExtents = halfExtents;
        normalsGeometry.center = center;

        normalsGeometry.primitive = gd.PRIMITIVE_LINES;
        normalsGeometry.semantics = gd.createSemantics([gd.SEMANTIC_POSITION]);
        normalsGeometry.vertexBuffer = normalsVertexBuffer;
        normalsGeometry.numIndices = normalsNumVerts;

        var normalsSurface = {
            first: 0,
            numVertices: normalsNumVerts,
            primitive: gd.PRIMITIVE_LINES
        };

        var normalGeometryInstance = GeometryInstance.create(normalsGeometry, normalsSurface, material);
        node.addRenderable(normalGeometryInstance);

        return normalGeometryInstance;
    };

    var writeNormals = function writeNormalsFn(normalsVertexBuffer, vertexBufferData, positionOffset, normalOffset, normalsNumVerts, scaleAll) {
        var length, normScale;
        var pos0, pos1, pos2;
        var norm0, norm1, norm2;
        var offset = 0;

        var writer = normalsVertexBuffer.map();
        if (writer) {
            for (var n = 0; n < normalsNumVerts; n += 2) {
                pos0 = vertexBufferData[offset + positionOffset + 0];
                pos1 = vertexBufferData[offset + positionOffset + 1];
                pos2 = vertexBufferData[offset + positionOffset + 2];

                norm0 = vertexBufferData[offset + normalOffset + 0];
                norm1 = vertexBufferData[offset + normalOffset + 1];
                norm2 = vertexBufferData[offset + normalOffset + 2];

                length = (norm0 * norm0) + (norm1 * norm1) + (norm2 * norm2);
                if (length) {
                    normScale = (1 / length) * scaleAll;
                    norm0 *= normScale;
                    norm1 *= normScale;
                    norm2 *= normScale;
                } else {
                    norm0 = 0;
                    norm1 = 0;
                    norm2 = 0;
                }

                writer(pos0, pos1, pos2);
                writer(pos0 + norm0, pos1 + norm1, pos2 + norm2);

                offset += stride;
            }
            normalsVertexBuffer.unmap(writer);
        }
    };

    var sceneNodes = this.rootNodes;
    var numNodes = sceneNodes.length;
    for (var i = 0; i < numNodes; i += 1) {
        node = sceneNodes[i];
        if (node.renderables) {
            var normalsNumVerts;

            var geometry;
            var surface;
            var vertexBuffer;
            var numVerts;
            var vertexBufferData;
            var positionOffset;
            var scaleGeometry;

            var normalOffset;
            var normalRenderable;
            var tangentOffset;
            var tangentRenderable;
            var binormalOffset;
            var binormalRenderable;

            var renderablesLength = node.renderables.length;
            for (var j = 0; j < renderablesLength; j += 1) {
                var renderable = node.renderables[j];

                if (!renderable.isNormal) {
                    geometry = renderable.geometry;
                    surface = renderable.surface;
                    halfExtents = geometry.halfExtents;
                    vertexBuffer = geometry.vertexBuffer;
                    scaleGeometry = (halfExtents[0] + halfExtents[1] + halfExtents[2]) * 0.01;

                    if (!renderable.normalsInfo) {
                        var first;
                        if (surface.indexBuffer) {
                            var vertexBufferAllocation = geometry.vertexBufferAllocation;
                            first = vertexBufferAllocation.baseIndex;
                            numVerts = vertexBufferAllocation.length;
                        } else {
                            first = surface.first;
                            numVerts = surface.numVertices;
                        }

                        var semantics = geometry.semantics;
                        var numSemantics = semantics.length;
                        normalsNumVerts = numVerts * 2;
                        stride = vertexBuffer.stride;
                        vertexBufferData = surface.vertexData;

                        var attributes = vertexBuffer.attributes;
                        var numAttributes = attributes.length;
                        center = geometry.center;

                        normalOffset = -1;
                        tangentOffset = -1;
                        binormalOffset = -1;
                        positionOffset = -1;
                        normalRenderable = null;
                        tangentRenderable = null;
                        binormalRenderable = null;

                        var offset = 0;
                        var semantic, attribute;

                        /* debug.assert(numAttributes === numSemantics); */

                        for (var n = 0; n < numSemantics; n += 1) {
                            semantic = semantics[n];
                            attribute = attributes[n];
                            if (gd.SEMANTIC_POSITION === semantic) {
                                positionOffset = offset;
                            } else if (gd.SEMANTIC_NORMAL === semantic) {
                                normalRenderable = createNormalsGeometryInstance(normalsNumVerts, normalMaterial);
                                normalRenderable.normals = true;
                                normalOffset = offset;
                            } else if (gd.SEMANTIC_TANGENT === semantic) {
                                tangentRenderable = createNormalsGeometryInstance(normalsNumVerts, tangentMaterial);
                                tangentRenderable.tangents = true;
                                tangentOffset = offset;
                            } else if (gd.SEMANTIC_BINORMAL === semantic) {
                                binormalRenderable = createNormalsGeometryInstance(normalsNumVerts, binormalMaterial);
                                binormalRenderable.binormal = true;
                                binormalOffset = offset;
                            }

                            var numComponents = this.attributeComponents(attribute);
                            offset += numComponents;
                        }

                        /* debug.assert(positionOffset !== -1); */

                        renderable.normalsInfo = {
                            stride: stride,
                            positionOffset: positionOffset,
                            vertexBufferData: vertexBufferData,
                            normalsNumVerts: normalsNumVerts,
                            normalOffset: normalOffset,
                            tangentOffset: tangentOffset,
                            binormalOffset: binormalOffset,
                            normalRenderable: normalRenderable,
                            tangentRenderable: tangentRenderable,
                            binormalRenderable: binormalRenderable,
                            scale: scaleGeometry
                        };
                    } else {
                        var normalsInfo = renderable.normalsInfo;
                        stride = normalsInfo.stride;
                        positionOffset = normalsInfo.positionOffset;
                        vertexBufferData = normalsInfo.vertexBufferData;
                        normalsNumVerts = normalsInfo.normalsNumVerts;

                        normalRenderable = normalsInfo.normalRenderable;
                        tangentRenderable = normalsInfo.tangentRenderable;
                        binormalRenderable = normalsInfo.binormalRenderable;

                        normalOffset = normalsInfo.normalOffset;
                        tangentOffset = normalsInfo.tangentOffset;
                        binormalOffset = normalsInfo.binormalOffset;

                        scaleGeometry = normalsInfo.scale;
                    }

                    if (normalRenderable) {
                        normalRenderable.disabled = !drawNormals;
                        if (drawNormals) {
                            normalRenderable.setMaterial(normalMaterial);
                            writeNormals(normalRenderable.geometry.vertexBuffer, vertexBufferData, positionOffset, normalOffset, normalsNumVerts, scale * scaleGeometry);
                        }
                    }
                    if (tangentRenderable) {
                        tangentRenderable.disabled = !drawTangents;
                        if (drawTangents) {
                            tangentRenderable.setMaterial(tangentMaterial);
                            writeNormals(tangentRenderable.geometry.vertexBuffer, vertexBufferData, positionOffset, tangentOffset, normalsNumVerts, scale * scaleGeometry);
                        }
                    }
                    if (binormalRenderable) {
                        binormalRenderable.disabled = !drawBinormals;
                        if (drawBinormals) {
                            binormalRenderable.setMaterial(binormalMaterial);
                            writeNormals(binormalRenderable.geometry.vertexBuffer, vertexBufferData, positionOffset, binormalOffset, normalsNumVerts, scale * scaleGeometry);
                        }
                    }
                }
            }
        }
    }
};

//
//
//
Scene.prototype.attributeComponents = function attributeComponentsFn(attribute) {
    if (TurbulenzEngine.canvas) {
        // Shortcut for canvas mode
        return attribute.numComponents;
    }

    var attrToComponents = this.vertexAttrToNumComponents;
    if (!attrToComponents) {
        var gd = TurbulenzEngine.getGraphicsDevice();

        attrToComponents = {};
        attrToComponents[gd.VERTEXFORMAT_BYTE4] = 4;
        attrToComponents[gd.VERTEXFORMAT_BYTE4N] = 4;
        attrToComponents[gd.VERTEXFORMAT_UBYTE4] = 4;
        attrToComponents[gd.VERTEXFORMAT_UBYTE4N] = 4;
        attrToComponents[gd.VERTEXFORMAT_SHORT4] = 4;
        attrToComponents[gd.VERTEXFORMAT_SHORT4N] = 4;
        attrToComponents[gd.VERTEXFORMAT_USHORT4] = 4;
        attrToComponents[gd.VERTEXFORMAT_USHORT4N] = 4;
        attrToComponents[gd.VERTEXFORMAT_FLOAT4] = 4;
        attrToComponents[gd.VERTEXFORMAT_SHORT2] = 2;
        attrToComponents[gd.VERTEXFORMAT_SHORT2N] = 2;
        attrToComponents[gd.VERTEXFORMAT_USHORT2] = 2;
        attrToComponents[gd.VERTEXFORMAT_USHORT2N] = 2;
        attrToComponents[gd.VERTEXFORMAT_FLOAT2] = 2;
        attrToComponents[gd.VERTEXFORMAT_FLOAT1] = 1;
        attrToComponents[gd.VERTEXFORMAT_FLOAT3] = 3;

        this.vertexAttrToNumComponents = attrToComponents;
    }

    var numComponents = attrToComponents[attribute];
    /* debug.assert(numComponents, "Unknown attribute type"); */
    return numComponents;
};

//
// drawWireframe
//
Scene.prototype.drawWireframe = function drawWireframeFn(gd, sm, camera, wireframeInfo) {
    var nodes = this.visibleNodes;
    var numNodes = nodes.length;
    if (numNodes) {
        var shader = sm.load("shaders/debug.cgfx");
        var technique = shader.getTechnique("wireframe");
        var technique_skinned = shader.getTechnique("wireframe_skinned");
        if (!technique || !technique_skinned) {
            return false;
        }

        var md = this.md;

        var setTechnique = gd.setTechnique;
        var setStream = gd.setStream;
        var draw = gd.draw;
        var m43MulM44 = md.m43MulM44;

        var vpm = camera.viewProjectionMatrix;

        var currentTechnique, wvp, wireframeSemantics, attributes, numAttributeComponents, numBlendComponents;

        var vformatFloat4 = gd.VERTEXFORMAT_FLOAT4;
        var vformatFloat3 = gd.VERTEXFORMAT_FLOAT3;
        var vformatByte4 = gd.VERTEXFORMAT_BYTE4;

        var skinnedAttributes = [
            vformatFloat4,
            vformatFloat3,
            vformatFloat3,
            vformatByte4,
            vformatFloat4,
            vformatByte4,
            vformatFloat4,
            vformatByte4,
            vformatFloat4
        ];
        var solidAttributes = [
            vformatFloat4,
            vformatFloat3,
            vformatFloat3
        ];

        var skinnedWireframeSemantics = this.skinnedWireframeSemantics;
        if (!skinnedWireframeSemantics) {
            skinnedWireframeSemantics = gd.createSemantics(['POSITION', 'TEXCOORD0', 'TEXCOORD1', 'BLENDINDICES', 'BLENDWEIGHT', 'TEXCOORD2', 'TEXCOORD3', 'TEXCOORD4', 'TEXCOORD5']);
            this.skinnedWireframeSemantics = skinnedWireframeSemantics;
        }

        var solidWireframeSemantics = this.solidWireframeSemantics;
        if (!solidWireframeSemantics) {
            solidWireframeSemantics = gd.createSemantics(['POSITION', 'TEXCOORD0', 'TEXCOORD1']);
            this.solidWireframeSemantics = solidWireframeSemantics;
        }

        for (var n = 0; n < numNodes; n += 1) {
            var node = nodes[n];
            var renderables = node.renderables;
            if (renderables && !node.disabled) {
                var numRenderables = renderables.length;
                for (var i = 0; i < numRenderables; i += 1) {
                    var renderable = renderables[i];
                    var oldSurface = renderable.surface;

                    if (!renderable.disabled && oldSurface && oldSurface.vertexData && (oldSurface.primitive === gd.PRIMITIVE_TRIANGLES || oldSurface.primitive === gd.PRIMITIVE_TRIANGLE_STRIP || oldSurface.primitive === gd.PRIMITIVE_TRIANGLE_FAN)) {
                        var surfacePrimitive = oldSurface.primitive;
                        var skinController = renderable.skinController;
                        if (skinController) {
                            if (currentTechnique !== technique_skinned) {
                                currentTechnique = technique_skinned;
                                setTechnique.call(gd, technique_skinned);
                            }

                            currentTechnique.skinBones = skinController.output;

                            attributes = skinnedAttributes;
                            wireframeSemantics = skinnedWireframeSemantics;
                            numAttributeComponents = 34;
                            numBlendComponents = 24;
                        } else {
                            if (currentTechnique !== technique) {
                                currentTechnique = technique;
                                setTechnique.call(gd, technique);
                            }

                            attributes = solidAttributes;
                            wireframeSemantics = solidWireframeSemantics;
                            numAttributeComponents = 10;
                            numBlendComponents = 0;
                        }

                        wvp = m43MulM44.call(md, renderable.node.world, vpm, wvp);
                        currentTechnique.worldViewProjection = wvp;
                        currentTechnique.windowScale = [gd.width / 2, gd.height / 2];
                        if (wireframeInfo && wireframeInfo.wireColor && wireframeInfo.fillColor) {
                            currentTechnique.wireColor = wireframeInfo.wireColor;
                            currentTechnique.fillColor = wireframeInfo.fillColor;
                            currentTechnique.alphaRef = wireframeInfo.alphaRef;
                        } else {
                            currentTechnique.wireColor = md.v4Build(0, 0, 0, 1);
                            currentTechnique.fillColor = md.v4Build(1, 1, 1, 0);

                            //leave alpha as zero to allow removing interior of polygons
                            currentTechnique.alphaRef = 0.35;
                        }

                        var wireframeBuffer = oldSurface.wireframeBuffer;
                        if (!wireframeBuffer) {
                            var oldGeometry = renderable.geometry;
                            var oldVertexBuffer = oldGeometry.vertexBuffer;
                            var oldSemantics = oldGeometry.semantics;
                            var oldVertexBufferData = oldSurface.vertexData;
                            var indexBuffer = oldSurface.indexBuffer;

                            var stride = oldVertexBuffer.stride;
                            var positionOffset = 0;
                            var blendIndicesOffset = 0;
                            var blendWeightOffset = 0;

                            var semanticFound = false;
                            for (var j = 0; j < oldSemantics.length; j += 1) {
                                if (oldSemantics[j] !== gd.SEMANTIC_POSITION) {
                                    positionOffset += Scene.prototype.attributeComponents(oldVertexBuffer.attributes[j]);
                                } else {
                                    semanticFound = true;
                                    break;
                                }
                            }
                            if (semanticFound === false) {
                                return false;
                            }

                            if (currentTechnique === technique_skinned) {
                                semanticFound = false;
                                for (j = 0; j < oldSemantics.length; j += 1) {
                                    if (oldSemantics[j] !== gd.SEMANTIC_BLENDINDICES) {
                                        blendIndicesOffset += Scene.prototype.attributeComponents(oldVertexBuffer.attributes[j]);
                                    } else {
                                        semanticFound = true;
                                        break;
                                    }
                                }
                                if (semanticFound === false) {
                                    return false;
                                }

                                semanticFound = false;
                                for (j = 0; j < oldSemantics.length; j += 1) {
                                    if (oldSemantics[j] !== gd.SEMANTIC_BLENDWEIGHT) {
                                        blendWeightOffset += Scene.prototype.attributeComponents(oldVertexBuffer.attributes[j]);
                                    } else {
                                        semanticFound = true;
                                        break;
                                    }
                                }
                                if (semanticFound === false) {
                                    return false;
                                }
                            }

                            var indexBufferData, vertexBuffer, dataLength, numTriangles, k, stepSize;
                            var vData = [];
                            var dstIndex = 0;
                            var vdIndex0, vdValue0x, vdValue0y, vdValue0z, vdIndex1, vdValue1x, vdValue1y, vdValue1z, vdIndex2, vdValue2x, vdValue2y, vdValue2z;

                            if (indexBuffer) {
                                indexBufferData = oldSurface.indexData;
                                if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_STRIP) {
                                    numTriangles = indexBufferData.length - 2;
                                    dataLength = numTriangles * 3;
                                    stepSize = 1;
                                } else {
                                    dataLength = indexBufferData.length;
                                    stepSize = 3;
                                }
                            } else if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_STRIP || surfacePrimitive === gd.PRIMITIVE_TRIANGLE_FAN) {
                                positionOffset += (oldSurface.first - oldGeometry.vertexBufferAllocation.baseIndex) * stride;
                                numTriangles = oldSurface.numVertices - 2;
                                dataLength = numTriangles * 3;
                                stepSize = 1;
                            } else {
                                positionOffset += (oldSurface.first - oldGeometry.vertexBufferAllocation.baseIndex) * stride;
                                dataLength = oldSurface.numVertices;
                                stepSize = 3;
                            }

                            vertexBuffer = gd.createVertexBuffer({
                                numVertices: dataLength,
                                attributes: attributes,
                                dynamic: oldVertexBuffer.dynamic
                            });

                            vData.length = dataLength * numAttributeComponents;

                            for (j = 0; j < dataLength; j += stepSize) {
                                if (indexBuffer) {
                                    if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_STRIP) {
                                        if ((j % 2) === 0) {
                                            vdIndex0 = indexBufferData[j] * stride + positionOffset;
                                            vdIndex1 = indexBufferData[(j + 1)] * stride + positionOffset;
                                            vdIndex2 = indexBufferData[(j + 2)] * stride + positionOffset;
                                        } else {
                                            vdIndex0 = indexBufferData[(j + 1)] * stride + positionOffset;
                                            vdIndex1 = indexBufferData[j] * stride + positionOffset;
                                            vdIndex2 = indexBufferData[(j + 2)] * stride + positionOffset;
                                        }
                                    } else {
                                        vdIndex0 = indexBufferData[j] * stride + positionOffset;
                                        vdIndex1 = indexBufferData[j + 1] * stride + positionOffset;
                                        vdIndex2 = indexBufferData[j + 2] * stride + positionOffset;
                                    }
                                } else if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_STRIP) {
                                    if ((j % 2) === 0) {
                                        vdIndex0 = j * stride + positionOffset;
                                        vdIndex1 = (j + 1) * stride + positionOffset;
                                        vdIndex2 = (j + 2) * stride + positionOffset;
                                    } else {
                                        vdIndex0 = (j + 1) * stride + positionOffset;
                                        vdIndex1 = j * stride + positionOffset;
                                        vdIndex2 = (j + 2) * stride + positionOffset;
                                    }
                                } else if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_FAN) {
                                    vdIndex0 = positionOffset;
                                    vdIndex1 = (j + 1) * stride + positionOffset;
                                    vdIndex2 = (j + 2) * stride + positionOffset;
                                } else {
                                    vdIndex0 = j * stride + positionOffset;
                                    vdIndex1 = (j + 1) * stride + positionOffset;
                                    vdIndex2 = (j + 2) * stride + positionOffset;
                                }

                                //Vertex 0
                                vdValue0x = oldVertexBufferData[vdIndex0];
                                vdValue0y = oldVertexBufferData[vdIndex0 + 1];
                                vdValue0z = oldVertexBufferData[vdIndex0 + 2];
                                vData[dstIndex] = vdValue0x;
                                vData[dstIndex + 1] = vdValue0y;
                                vData[dstIndex + 2] = vdValue0z;
                                vData[dstIndex + 3] = 0;

                                //Vertex 1 passed as attribute of Vertex 0
                                vdValue1x = oldVertexBufferData[vdIndex1];
                                vdValue1y = oldVertexBufferData[vdIndex1 + 1];
                                vdValue1z = oldVertexBufferData[vdIndex1 + 2];
                                vData[dstIndex + 4] = vdValue1x;
                                vData[dstIndex + 5] = vdValue1y;
                                vData[dstIndex + 6] = vdValue1z;

                                //Vertex 2 passed as attribute of Vertex 0
                                vdValue2x = oldVertexBufferData[vdIndex2];
                                vdValue2y = oldVertexBufferData[vdIndex2 + 1];
                                vdValue2z = oldVertexBufferData[vdIndex2 + 2];
                                vData[dstIndex + 7] = vdValue2x;
                                vData[dstIndex + 8] = vdValue2y;
                                vData[dstIndex + 9] = vdValue2z;

                                //Depending on whether skinned or not, increments accordingly
                                dstIndex += numAttributeComponents;

                                //Repeat for Vertex 1
                                vData[dstIndex] = vdValue1x;
                                vData[dstIndex + 1] = vdValue1y;
                                vData[dstIndex + 2] = vdValue1z;
                                vData[dstIndex + 3] = 1;
                                vData[dstIndex + 4] = vdValue0x;
                                vData[dstIndex + 5] = vdValue0y;
                                vData[dstIndex + 6] = vdValue0z;
                                vData[dstIndex + 7] = vdValue2x;
                                vData[dstIndex + 8] = vdValue2y;
                                vData[dstIndex + 9] = vdValue2z;
                                dstIndex += numAttributeComponents;

                                //Repeat for Vertex 2
                                vData[dstIndex] = vdValue2x;
                                vData[dstIndex + 1] = vdValue2y;
                                vData[dstIndex + 2] = vdValue2z;
                                vData[dstIndex + 3] = 2;
                                vData[dstIndex + 4] = vdValue0x;
                                vData[dstIndex + 5] = vdValue0y;
                                vData[dstIndex + 6] = vdValue0z;
                                vData[dstIndex + 7] = vdValue1x;
                                vData[dstIndex + 8] = vdValue1y;
                                vData[dstIndex + 9] = vdValue1z;
                                dstIndex += numAttributeComponents;
                            }

                            if (currentTechnique === technique_skinned) {
                                var vdIndex0i, vdIndex0w, vdIndex1i, vdIndex1w, vdIndex2i, vdIndex2w;
                                var vdValue0iw = [];
                                var vdValue1iw = [];
                                var vdValue2iw = [];
                                dstIndex = 0;
                                for (j = 0; j < dataLength; j += stepSize) {
                                    if (indexBuffer) {
                                        vdIndex0i = indexBufferData[j] * stride + blendIndicesOffset;
                                        vdIndex1i = indexBufferData[j + 1] * stride + blendIndicesOffset;
                                        vdIndex2i = indexBufferData[j + 2] * stride + blendIndicesOffset;
                                        vdIndex0w = indexBufferData[j] * stride + blendWeightOffset;
                                        vdIndex1w = indexBufferData[j + 1] * stride + blendWeightOffset;
                                        vdIndex2w = indexBufferData[j + 2] * stride + blendWeightOffset;
                                    } else if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_STRIP) {
                                        if ((j % 2) === 0) {
                                            vdIndex0i = j * stride + blendIndicesOffset;
                                            vdIndex1i = (j + 1) * stride + blendIndicesOffset;
                                            vdIndex2i = (j + 2) * stride + blendIndicesOffset;
                                            vdIndex0w = j * stride + blendWeightOffset;
                                            vdIndex1w = (j + 1) * stride + blendWeightOffset;
                                            vdIndex2w = (j + 2) * stride + blendWeightOffset;
                                        } else {
                                            vdIndex0i = (j + 1) * stride + blendIndicesOffset;
                                            vdIndex1i = j * stride + blendIndicesOffset;
                                            vdIndex2i = (j + 2) * stride + blendIndicesOffset;
                                            vdIndex0w = (j + 1) * stride + blendWeightOffset;
                                            vdIndex1w = j * stride + blendWeightOffset;
                                            vdIndex2w = (j + 2) * stride + blendWeightOffset;
                                        }
                                    } else if (surfacePrimitive === gd.PRIMITIVE_TRIANGLE_FAN) {
                                        vdIndex0i = blendIndicesOffset;
                                        vdIndex1i = (j + 1) * stride + blendIndicesOffset;
                                        vdIndex2i = (j + 2) * stride + blendIndicesOffset;
                                        vdIndex0w = blendWeightOffset;
                                        vdIndex1w = (j + 1) * stride + blendWeightOffset;
                                        vdIndex2w = (j + 2) * stride + blendWeightOffset;
                                    } else {
                                        vdIndex0i = j * stride + blendIndicesOffset;
                                        vdIndex1i = (j + 1) * stride + blendIndicesOffset;
                                        vdIndex2i = (j + 2) * stride + blendIndicesOffset;
                                        vdIndex0w = j * stride + blendWeightOffset;
                                        vdIndex1w = (j + 1) * stride + blendWeightOffset;
                                        vdIndex2w = (j + 2) * stride + blendWeightOffset;
                                    }

                                    for (k = 0; k < 4; k += 1) {
                                        vdValue0iw[k] = oldVertexBufferData[vdIndex0i + k];
                                        vdValue0iw[k + 4] = oldVertexBufferData[vdIndex0w + k];
                                        vdValue1iw[k] = oldVertexBufferData[vdIndex1i + k];
                                        vdValue1iw[k + 4] = oldVertexBufferData[vdIndex1w + k];
                                        vdValue2iw[k] = oldVertexBufferData[vdIndex2i + k];
                                        vdValue2iw[k + 4] = oldVertexBufferData[vdIndex2w + k];
                                    }
                                    for (k = 0; k < 8; k += 1) {
                                        vData[dstIndex + 10 + k] = vdValue0iw[k];
                                        vData[dstIndex + 18 + k] = vdValue1iw[k];
                                        vData[dstIndex + 26 + k] = vdValue2iw[k];
                                    }
                                    dstIndex += numAttributeComponents;

                                    for (k = 0; k < 8; k += 1) {
                                        vData[dstIndex + 10 + k] = vdValue1iw[k];
                                        vData[dstIndex + 18 + k] = vdValue0iw[k];
                                        vData[dstIndex + 26 + k] = vdValue2iw[k];
                                    }
                                    dstIndex += numAttributeComponents;

                                    for (k = 0; k < 8; k += 1) {
                                        vData[dstIndex + 10 + k] = vdValue2iw[k];
                                        vData[dstIndex + 18 + k] = vdValue0iw[k];
                                        vData[dstIndex + 26 + k] = vdValue1iw[k];
                                    }
                                    dstIndex += numAttributeComponents;
                                }
                            }

                            vertexBuffer.setData(vData);
                            indexBuffer = null;
                            oldSurface.wireframeBuffer = vertexBuffer;
                            wireframeBuffer = vertexBuffer;
                        }

                        setStream.call(gd, wireframeBuffer, wireframeSemantics);
                        draw.call(gd, gd.PRIMITIVE_TRIANGLES, wireframeBuffer.numVertices, 0);
                    }
                }
            }
        }
    }
    return true;
};

//
// getMetrics
//
Scene.prototype.getMetrics = function getMetricsFn() {
    var numTotalNodes = 0;
    var numTotalRenderables = 0;
    var numTotalVertices = 0;
    var numTotalPrimitives = 0;
    var numTotalLights = this.globalLights.length;

    var count = function countFn(node) {
        numTotalNodes += 1;

        var renderables = node.renderables;
        var numRenderables = (renderables ? renderables.length : 0);
        numTotalRenderables += numRenderables;

        var lights = node.lightInstances;
        var numLights = (lights ? lights.length : 0);
        numTotalLights += numLights;

        var n, numPrimitives;
        for (n = 0; n < numRenderables; n += 1) {
            var renderable = renderables[n];
            var surface = renderable.surface;
            numTotalVertices += surface.numVertices;
            if (surface.indexBuffer) {
                numPrimitives = surface.numIndices;
            } else {
                numPrimitives = surface.numVertices;
            }
            switch (surface.primitive) {
                case 0:
                    numTotalPrimitives += numPrimitives;
                    break;
                case 1:
                    numTotalPrimitives += (numPrimitives >> 1);
                    break;
                case 2:
                    numTotalPrimitives += numPrimitives;
                    break;
                case 3:
                    numTotalPrimitives += (numPrimitives - 1);
                    break;
                case 4:
                    numTotalPrimitives += (numPrimitives / 3) | 0;
                    break;
                case 5:
                    numTotalPrimitives += (numPrimitives - 2);
                    break;
                case 6:
                    numTotalPrimitives += (numPrimitives - 2);
                    break;
            }
            ;
        }

        var children = node.children;
        if (children) {
            var numChildren = children.length;
            for (n = 0; n < numChildren; n += 1) {
                count(children[n]);
            }
        }
    };

    var rootNodes = this.rootNodes;
    if (rootNodes) {
        var numRoots = rootNodes.length;
        for (var n = 0; n < numRoots; n += 1) {
            count(rootNodes[n]);
        }
    }

    return {
        numNodes: numTotalNodes,
        numRenderables: numTotalRenderables,
        numLights: numTotalLights,
        numVertices: numTotalVertices,
        numPrimitives: numTotalPrimitives
    };
};

//
// getVisibilityMetrics
//
Scene.prototype.getVisibilityMetrics = function getVisibilityMetricsFn() {
    var visiblePortals = this.visiblePortals;
    var numVisiblePortals = visiblePortals.length;

    var numPortalsPlanes = 0;
    var n;
    for (n = 0; n < numVisiblePortals; n += 1) {
        numPortalsPlanes += visiblePortals[n].planes.length;
    }

    var numRenderables = this.visibleRenderables.length;
    var numShadowMaps = 0, numOccluders = 0;
    var visibleLights = this.visibleLights;
    var numLights = visibleLights.length;
    for (n = 0; n < numLights; n += 1) {
        var lightInstance = visibleLights[n];
        if (lightInstance.numVisibleDrawParameters) {
            numRenderables += lightInstance.numVisibleDrawParameters;
        }
        var shadowMap = lightInstance.shadowMap;
        if (shadowMap) {
            if (lightInstance.frameVisible === shadowMap.frameVisible && shadowMap.numRenderables) {
                numShadowMaps += 1;
                numOccluders += shadowMap.numRenderables;
            }
        }
    }

    return {
        numPortals: numVisiblePortals,
        numPortalsPlanes: numPortalsPlanes,
        numLights: numLights,
        numRenderables: numRenderables,
        numShadowMaps: numShadowMaps,
        numOccluders: numOccluders
    };
};

//
// getDebugSemanticsPosCol
//
Scene.prototype.getDebugSemanticsPosCol = function getDebugSemanticsPosColFn() {
    var debugSemantics = this.debugSemanticsPosCol;
    if (!debugSemantics) {
        var gd = TurbulenzEngine.getGraphicsDevice();
        debugSemantics = gd.createSemantics([
            gd.SEMANTIC_POSITION,
            gd.SEMANTIC_COLOR
        ]);
        this.debugSemanticsPosCol = debugSemantics;
    }
    return debugSemantics;
};

//
// getDebugSemanticsPos
//
Scene.prototype.getDebugSemanticsPos = function getDebugSemanticsPosFn() {
    var debugSemantics = this.debugSemanticsPos;
    if (!debugSemantics) {
        var gd = TurbulenzEngine.getGraphicsDevice();
        debugSemantics = gd.createSemantics([gd.SEMANTIC_POSITION]);
        this.debugSemanticsPos = debugSemantics;
    }
    return debugSemantics;
};


// Copyright (c) 2010-2014 Turbulenz Limited
;

//
// physicsmanager
//
var PhysicsManager = (function () {
    function PhysicsManager() {
    }
    //
    // addNode
    //
    PhysicsManager.prototype.addNode = function (sceneNode, physicsObject, origin, triangleArray) {
        var physicsNode = {
            body: physicsObject,
            target: sceneNode
        };

        physicsObject.userData = sceneNode;

        if (origin) {
            physicsNode.origin = origin;
        }

        if (triangleArray) {
            physicsNode.triangleArray = triangleArray;
        }

        if (physicsObject.kinematic) {
            physicsNode.kinematic = true;

            sceneNode.setDynamic();
            sceneNode.kinematic = true;

            this.kinematicPhysicsNodes.push(physicsNode);
        } else if ("mass" in physicsObject) {
            physicsNode.dynamic = true;

            sceneNode.setDynamic();

            this.dynamicPhysicsNodes.push(physicsNode);
        }

        var targetPhysicsNodes = sceneNode.physicsNodes;
        if (targetPhysicsNodes) {
            targetPhysicsNodes.push(physicsNode);
        } else {
            sceneNode.physicsNodes = [physicsNode];
            this.subscribeSceneNode(sceneNode);
        }

        this.physicsNodes.push(physicsNode);

        this.enableHierarchy(sceneNode, true);
    };

    //
    // update
    //
    PhysicsManager.prototype.update = function () {
        var mathsDevice = this.mathsDevice;

        // Dynamic nodes
        var physicsNodes = this.dynamicPhysicsNodes;
        var numPhysicsNodes = physicsNodes.length;
        var physicsNode, body, target, worldMatrix, origin, n;
        if (numPhysicsNodes > 0) {
            for (n = 0; n < numPhysicsNodes; n += 1) {
                physicsNode = physicsNodes[n];
                body = physicsNode.body;
                if (body.active) {
                    target = physicsNode.target;
                    if (target.disabled) {
                        continue;
                    }

                    if (target.parent) {
                        /* debug.abort("Rigid bodies with parent nodes are unsupported"); */
                        //Not really possible, since the child can become inactive (frozen) and therefore it will
                        /*var parentWorld = target.parent.getWorldTransform();
                        var inverseParent = mathsDevice.m43Inverse(parentWorld);
                        var newLocal = mathsDevice.m43Mul(worldMatrix, inverseParent);
                        target.setLocalTransform(newLocal);*/
                    } else {
                        worldMatrix = target.getLocalTransform();
                        body.calculateTransform(worldMatrix, physicsNode.origin);
                        target.setLocalTransform(worldMatrix);
                    }
                }
            }
        }

        // Kinematic nodes
        var tempMatrix = this.tempMatrix;
        physicsNodes = this.kinematicPhysicsNodes;
        numPhysicsNodes = physicsNodes.length;
        for (n = 0; n < numPhysicsNodes; n += 1) {
            physicsNode = physicsNodes[n];
            target = physicsNode.target;
            if (target.disabled) {
                continue;
            }

            if (target.worldUpdate !== physicsNode.worldUpdate) {
                physicsNode.worldUpdate = target.worldUpdate;
                worldMatrix = target.getWorldTransform();
                origin = physicsNode.origin;
                if (origin) {
                    // The physics API copies the matrix instead of referencing it
                    // so it is safe to share a temp one
                    physicsNode.body.transform = mathsDevice.m43Offset(worldMatrix, origin, tempMatrix);
                } else {
                    physicsNode.body.transform = worldMatrix;
                }
            }
        }
    };

    //
    // enableNode
    //
    PhysicsManager.prototype.enableNode = function (sceneNode, enabled) {
        var physicsNodes = sceneNode.physicsNodes;

        if (physicsNodes) {
            var dynamicsWorld = this.dynamicsWorld;
            var numPhysicsNodes = physicsNodes.length;
            for (var p = 0; p < numPhysicsNodes; p += 1) {
                var physicsNode = physicsNodes[p];
                var body = physicsNode.body;
                if (body) {
                    if (physicsNode.kinematic) {
                        if (enabled) {
                            dynamicsWorld.addCollisionObject(body);
                        } else {
                            dynamicsWorld.removeCollisionObject(body);
                        }
                    } else if (physicsNode.dynamic) {
                        if (enabled) {
                            dynamicsWorld.addRigidBody(body);
                        } else {
                            dynamicsWorld.removeRigidBody(body);
                        }
                    } else {
                        if (enabled) {
                            dynamicsWorld.addCollisionObject(body);
                        } else {
                            dynamicsWorld.removeCollisionObject(body);
                        }
                    }
                }
            }
        }
    };

    //
    // enableHierarchy
    //
    PhysicsManager.prototype.enableHierarchy = function (sceneNode, enabled) {
        this.enableNode(sceneNode, enabled);

        var children = sceneNode.children;
        if (children) {
            var numChildren = children.length;
            for (var c = 0; c < numChildren; c += 1) {
                this.enableHierarchy(children[c], enabled);
            }
        }
    };

    //
    // deletePhysicsNode
    //
    PhysicsManager.prototype.deletePhysicsNode = function (physicsNode) {
        var physicsNodes = this.physicsNodes;
        var numPhysicsNodes = physicsNodes.length;
        var n;
        for (n = 0; n < numPhysicsNodes; n += 1) {
            if (physicsNodes[n] === physicsNode) {
                physicsNodes.splice(n, 1);
                break;
            }
        }

        physicsNodes = this.dynamicPhysicsNodes;
        numPhysicsNodes = physicsNodes.length;
        for (n = 0; n < numPhysicsNodes; n += 1) {
            if (physicsNodes[n] === physicsNode) {
                physicsNodes.splice(n, 1);
                break;
            }
        }

        physicsNodes = this.kinematicPhysicsNodes;
        numPhysicsNodes = physicsNodes.length;
        for (n = 0; n < numPhysicsNodes; n += 1) {
            if (physicsNodes[n] === physicsNode) {
                physicsNodes.splice(n, 1);
                break;
            }
        }
    };

    //
    // deleteNode
    //
    PhysicsManager.prototype.deleteNode = function (sceneNode) {
        var physicsNodes = sceneNode.physicsNodes;
        if (physicsNodes) {
            var physicsDevice = this.physicsDevice;
            var dynamicsWorld = this.dynamicsWorld;
            if (physicsDevice && dynamicsWorld) {
                var numPhysicsNodes = physicsNodes.length;
                for (var p = 0; p < numPhysicsNodes; p += 1) {
                    var physicsNode = physicsNodes[p];
                    var body = physicsNode.body;
                    if (body) {
                        if (physicsNode.kinematic) {
                            dynamicsWorld.removeCollisionObject(body);
                        } else if (physicsNode.dynamic) {
                            dynamicsWorld.removeRigidBody(body);
                        } else {
                            dynamicsWorld.removeCollisionObject(body);
                        }
                    }
                    this.deletePhysicsNode(physicsNode);
                }

                this.unsubscribeSceneNode(sceneNode);
                delete sceneNode.physicsNodes;
            }
        }
    };

    //
    // deleteHierarchy
    //
    PhysicsManager.prototype.deleteHierarchy = function (sceneNode) {
        this.deleteNode(sceneNode);

        var children = sceneNode.children;
        if (children) {
            var numChildren = children.length;
            for (var c = 0; c < numChildren; c += 1) {
                this.deleteHierarchy(children[c]);
            }
        }
    };

    //
    // calculateHierarchyExtents
    //
    PhysicsManager.prototype.calculateHierarchyExtents = function (sceneNode) {
        var min = Math.min;
        var max = Math.max;
        var maxValue = Number.MAX_VALUE;
        var arrayConstructor = this.arrayConstructor;

        /*jshint newcap: false*/
        var totalExtents = new arrayConstructor(6);

        /*jshint newcap: true*/
        totalExtents[2] = totalExtents[1] = totalExtents[0] = maxValue;
        totalExtents[5] = totalExtents[4] = totalExtents[3] = -maxValue;

        var calculateNodeExtents = function calculateNodeExtentsFn(sceneNode) {
            var physicsNodes = sceneNode.physicsNodes;
            if (physicsNodes) {
                var numPhysicsNodes = physicsNodes.length;

                /*jshint newcap: false*/
                var extents = new arrayConstructor(6);

                for (var p = 0; p < numPhysicsNodes; p += 1) {
                    physicsNodes[p].body.calculateExtents(extents);
                    totalExtents[0] = min(totalExtents[0], extents[0]);
                    totalExtents[1] = min(totalExtents[1], extents[1]);
                    totalExtents[2] = min(totalExtents[2], extents[2]);
                    totalExtents[3] = max(totalExtents[3], extents[3]);
                    totalExtents[4] = max(totalExtents[4], extents[4]);
                    totalExtents[5] = max(totalExtents[5], extents[5]);
                }
            }

            var children = sceneNode.children;
            if (children) {
                var numChildren = children.length;
                for (var n = 0; n < numChildren; n += 1) {
                    calculateNodeExtents(children[n]);
                }
            }
        };

        calculateNodeExtents(sceneNode);

        if (totalExtents[0] >= totalExtents[3]) {
            return undefined;
        }
        return totalExtents;
    };

    //
    // calculateExtents
    //
    PhysicsManager.prototype.calculateExtents = function (sceneNode) {
        var min = Math.min;
        var max = Math.max;
        var maxValue = Number.MAX_VALUE;
        var totalExtents = new this.arrayConstructor(6);
        totalExtents[2] = totalExtents[1] = totalExtents[0] = maxValue;
        totalExtents[5] = totalExtents[4] = totalExtents[3] = -maxValue;

        var physicsNodes = sceneNode.physicsNodes;
        if (physicsNodes) {
            var numPhysicsNodes = physicsNodes.length;
            var extents = new this.arrayConstructor(6);
            for (var p = 0; p < numPhysicsNodes; p += 1) {
                physicsNodes[p].body.calculateExtents(extents);
                totalExtents[0] = min(totalExtents[0], extents[0]);
                totalExtents[1] = min(totalExtents[1], extents[1]);
                totalExtents[2] = min(totalExtents[2], extents[2]);
                totalExtents[3] = max(totalExtents[3], extents[3]);
                totalExtents[4] = max(totalExtents[4], extents[4]);
                totalExtents[5] = max(totalExtents[5], extents[5]);
            }
        }

        if (totalExtents[0] >= totalExtents[3]) {
            return undefined;
        }
        return totalExtents;
    };

    //
    // clear
    //
    PhysicsManager.prototype.clear = function () {
        if (this.physicsNodes) {
            for (var index = 0; index < this.physicsNodes.length; index += 1) {
                this.unsubscribeSceneNode(this.physicsNodes[index].target);
            }
        }
        this.physicsNodes = [];
        this.dynamicPhysicsNodes = [];
        this.kinematicPhysicsNodes = [];
    };

    //
    // loadNodes
    //
    PhysicsManager.prototype.loadNodes = function (loadParams, scene) {
        var sceneData = loadParams.data;
        var collisionMargin = (loadParams.collisionMargin || 0.005);
        var positionMargin = collisionMargin * 0.1;
        var nodesNamePrefix = loadParams.nodesNamePrefix;

        if (!loadParams.append) {
            this.clear();
        }

        if (!this.physicsDevice) {
            return;
        }
        var physicsDevice = this.physicsDevice;
        var dynamicsWorld = this.dynamicsWorld;
        var dynamicFilterFlag = physicsDevice.FILTER_DYNAMIC;
        var kinematicFilterFlag = physicsDevice.FILTER_KINEMATIC;
        var staticFilterFlag = physicsDevice.FILTER_STATIC;
        var characterFilterFlag = physicsDevice.FILTER_CHARACTER;
        var projectileFilterFlag = physicsDevice.FILTER_PROJECTILE;
        var allFilterFlag = physicsDevice.FILTER_ALL;

        var mathsDevice = this.mathsDevice;
        var physicsNodes = this.physicsNodes;
        var dynamicPhysicsNodes = this.dynamicPhysicsNodes;
        var kinematicPhysicsNodes = this.kinematicPhysicsNodes;
        var fileShapes = sceneData.geometries;
        var fileNodes = sceneData.physicsnodes;
        var fileModels = sceneData.physicsmodels;
        var fileMaterials = sceneData.physicsmaterials;
        var shape, origin, triangleArray;
        for (var fn in fileNodes) {
            if (fileNodes.hasOwnProperty(fn)) {
                var fileNode = fileNodes[fn];
                var targetName = fileNode.target;
                if (nodesNamePrefix) {
                    targetName = SceneNode.makePath(nodesNamePrefix, targetName);
                }
                var target = scene.findNode(targetName);
                if (!target) {
                    continue;
                }
                var fileModel = fileModels[fileNode.body];
                if (!fileModel) {
                    continue;
                }
                var physicsMaterial;
                if (fileMaterials) {
                    physicsMaterial = fileMaterials[fileModel.material];
                }
                if (physicsMaterial && (physicsMaterial.nonsolid || physicsMaterial.far)) {
                    continue;
                }
                var kinematic = (fileModel.kinematic || target.kinematic);
                var dynamic = (fileModel.dynamic || target.dynamic);
                var disabled = target.disabled;
                shape = null;
                origin = null;
                triangleArray = null;
                var shapeType = fileModel.shape;
                if (shapeType === "box") {
                    var halfExtents = fileModel.halfExtents || fileModel.halfextents;
                    shape = physicsDevice.createBoxShape({
                        halfExtents: halfExtents,
                        margin: collisionMargin
                    });
                } else if (shapeType === "sphere") {
                    shape = physicsDevice.createSphereShape({
                        radius: fileModel.radius,
                        margin: collisionMargin
                    });
                } else if (shapeType === "cone") {
                    shape = physicsDevice.createConeShape({
                        radius: fileModel.radius,
                        height: fileModel.height,
                        margin: collisionMargin
                    });
                } else if (shapeType === "capsule") {
                    shape = physicsDevice.createCapsuleShape({
                        radius: fileModel.radius,
                        height: fileModel.height,
                        margin: collisionMargin
                    });
                } else if (shapeType === "cylinder") {
                    shape = physicsDevice.createCylinderShape({
                        halfExtents: [fileModel.radius, fileModel.height, fileModel.radius],
                        margin: collisionMargin
                    });
                } else if (shapeType === "convexhull" || shapeType === "mesh") {
                    var geometry = fileShapes[fileModel.geometry];
                    if (geometry) {
                        shape = geometry.physicsShape;
                        if (shape) {
                            origin = geometry.origin;
                        } else {
                            var inputs = geometry.inputs;
                            var inputPosition = inputs.POSITION;
                            var positions = geometry.sources[inputPosition.source];
                            var positionsData = positions.data;
                            var numPositionsValues = positionsData.length;
                            var posMin = positions.min;
                            var posMax = positions.max;
                            var np, pos0, pos1, pos2;
                            var min0, min1, min2, max0, max1, max2;
                            if (posMin && posMax) {
                                var centerPos0 = ((posMax[0] + posMin[0]) * 0.5);
                                var centerPos1 = ((posMax[1] + posMin[1]) * 0.5);
                                var centerPos2 = ((posMax[2] + posMin[2]) * 0.5);
                                if (Math.abs(centerPos0) > positionMargin || Math.abs(centerPos1) > positionMargin || Math.abs(centerPos2) > positionMargin) {
                                    var halfPos0 = ((posMax[0] - posMin[0]) * 0.5);
                                    var halfPos1 = ((posMax[1] - posMin[1]) * 0.5);
                                    var halfPos2 = ((posMax[2] - posMin[2]) * 0.5);
                                    min0 = -halfPos0;
                                    min1 = -halfPos1;
                                    min2 = -halfPos2;
                                    max0 = halfPos0;
                                    max1 = halfPos1;
                                    max2 = halfPos2;
                                    var newPositionsData = [];
                                    newPositionsData.length = numPositionsValues;
                                    for (np = 0; np < numPositionsValues; np += 3) {
                                        pos0 = (positionsData[np + 0] - centerPos0);
                                        pos1 = (positionsData[np + 1] - centerPos1);
                                        pos2 = (positionsData[np + 2] - centerPos2);
                                        if (min0 > pos0) {
                                            min0 = pos0;
                                        } else if (max0 < pos0) {
                                            max0 = pos0;
                                        }
                                        if (min1 > pos1) {
                                            min1 = pos1;
                                        } else if (max1 < pos1) {
                                            max1 = pos1;
                                        }
                                        if (min2 > pos2) {
                                            min2 = pos2;
                                        } else if (max2 < pos2) {
                                            max2 = pos2;
                                        }
                                        newPositionsData[np + 0] = pos0;
                                        newPositionsData[np + 1] = pos1;
                                        newPositionsData[np + 2] = pos2;
                                    }
                                    positionsData = newPositionsData;
                                    posMin = [min0, min1, min2];
                                    posMax = [max0, max1, max2];
                                    origin = mathsDevice.v3Build(centerPos0, centerPos1, centerPos2);
                                    geometry.origin = origin;
                                }
                            } else {
                                //TODO: add a warning that with no extents we can't calculate and origin?
                                geometry.origin = [0, 0, 0];
                            }

                            if (positionsData.length === 12) {
                                min0 = posMin[0];
                                min1 = posMin[1];
                                min2 = posMin[2];
                                max0 = posMax[0];
                                max1 = posMax[1];
                                max2 = posMax[2];
                                if (min0 === max0 || min1 === max1 || min2 === max2) {
                                    for (np = 0; np < 12; np += 3) {
                                        pos0 = positionsData[np + 0];
                                        pos1 = positionsData[np + 1];
                                        pos2 = positionsData[np + 2];
                                        if ((pos0 !== min0 && pos0 !== max0) || (pos1 !== min1 && pos1 !== max1) || (pos2 !== min2 && pos2 !== max2)) {
                                            break;
                                        }
                                    }

                                    if (np >= numPositionsValues) {
                                        shapeType = "box";

                                        shape = physicsDevice.createBoxShape({
                                            halfExtents: [
                                                (max0 - min0) * 0.5,
                                                (max1 - min1) * 0.5,
                                                (max2 - min2) * 0.5
                                            ],
                                            margin: collisionMargin
                                        });
                                    }
                                }
                            } else if (positionsData.length === 24) {
                                min0 = posMin[0];
                                min1 = posMin[1];
                                min2 = posMin[2];
                                max0 = posMax[0];
                                max1 = posMax[1];
                                max2 = posMax[2];

                                for (np = 0; np < 24; np += 3) {
                                    pos0 = positionsData[np + 0];
                                    pos1 = positionsData[np + 1];
                                    pos2 = positionsData[np + 2];
                                    if ((pos0 !== min0 && pos0 !== max0) || (pos1 !== min1 && pos1 !== max1) || (pos2 !== min2 && pos2 !== max2)) {
                                        break;
                                    }
                                }

                                if (np >= numPositionsValues) {
                                    shapeType = "box";

                                    shape = physicsDevice.createBoxShape({
                                        halfExtents: [
                                            (max0 - min0) * 0.5,
                                            (max1 - min1) * 0.5,
                                            (max2 - min2) * 0.5
                                        ],
                                        margin: collisionMargin
                                    });
                                }
                            }

                            if (shapeType === "convexhull") {
                                shape = physicsDevice.createConvexHullShape({
                                    points: positionsData,
                                    margin: collisionMargin,
                                    minExtent: posMin,
                                    maxExtent: posMax
                                });
                            } else if (shapeType === "mesh") {
                                var maxOffset = 0;
                                for (var input in inputs) {
                                    if (inputs.hasOwnProperty(input)) {
                                        var fileInput = inputs[input];
                                        var offset = fileInput.offset;
                                        if (offset > maxOffset) {
                                            maxOffset = offset;
                                        }
                                    }
                                }

                                var indices = [];
                                var surfaces = geometry.surfaces;
                                if (!surfaces) {
                                    surfaces = { s: { triangles: geometry.triangles } };
                                }
                                for (var surf in surfaces) {
                                    if (surfaces.hasOwnProperty(surf)) {
                                        var surface = surfaces[surf];

                                        if (maxOffset > 0) {
                                            var triangles = surface.triangles;
                                            if (triangles) {
                                                var indicesPerVertex = (maxOffset + 1);
                                                var numIndices = triangles.length;
                                                var positionsOffset = inputPosition.offset;
                                                for (var v = 0; v < numIndices; v += indicesPerVertex) {
                                                    indices.push(triangles[v + positionsOffset]);
                                                }
                                            }
                                        } else {
                                            var surfIndices = surface.triangles;
                                            if (surfIndices) {
                                                if (indices.length === 0) {
                                                    indices = surfIndices;
                                                } else {
                                                    var numSurfIndices = surfIndices.length;
                                                    for (var i = 0; i < numSurfIndices; i += 1) {
                                                        indices.push(surfIndices[i]);
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }

                                if (indices) {
                                    var triangleArrayParams = {
                                        vertices: positionsData,
                                        indices: indices,
                                        minExtent: posMin,
                                        maxExtent: posMax
                                    };
                                    triangleArray = physicsDevice.createTriangleArray(triangleArrayParams);
                                    if (triangleArray) {
                                        shape = physicsDevice.createTriangleMeshShape({
                                            triangleArray: triangleArray,
                                            margin: collisionMargin
                                        });
                                    }
                                }
                            }
                            geometry.physicsShape = shape;
                        }
                    }
                }

                if (shape) {
                    var transform = target.getWorldTransform();
                    if (origin) {
                        transform = mathsDevice.m43Offset(transform, origin);
                    }

                    // TODO: Declare this as a Physics*Parameters so
                    // we only have to initialize the required entries
                    // at this stage.
                    var params = {
                        shape: shape,
                        transform: transform,
                        friction: undefined,
                        restitution: undefined,
                        group: undefined,
                        mask: undefined,
                        kinematic: undefined,
                        mass: undefined,
                        inertia: undefined,
                        frozen: undefined,
                        linearVelocity: undefined,
                        angularVelocity: undefined
                    };

                    if (physicsMaterial) {
                        if (physicsMaterial.dynamic_friction) {
                            params.friction = physicsMaterial.dynamic_friction;
                        }
                        if (physicsMaterial.restitution) {
                            params.restitution = physicsMaterial.restitution;
                        }
                    }

                    // Check for filters to specify which groups will collide against these objects
                    var collisionFilters = allFilterFlag;
                    if (physicsMaterial) {
                        var materialFilter = physicsMaterial.collisionFilter;
                        if (materialFilter) {
                            collisionFilters = 0;
                            var numFilters = materialFilter.length;
                            for (var f = 0; f < numFilters; f += 1) {
                                var filter = materialFilter[f];
                                if (filter === "ALL") {
                                    collisionFilters += allFilterFlag;
                                } else if (filter === "DYNAMIC") {
                                    collisionFilters += dynamicFilterFlag;
                                } else if (filter === "CHARACTER") {
                                    collisionFilters += characterFilterFlag;
                                } else if (filter === "PROJECTILE") {
                                    collisionFilters += projectileFilterFlag;
                                } else if (filter === "STATIC") {
                                    collisionFilters += staticFilterFlag;
                                } else if (filter === "KINEMATIC") {
                                    collisionFilters += kinematicFilterFlag;
                                }
                            }
                            if (collisionFilters === 0) {
                                /* debug.log("Ignoring physics node without a collision mask: " + fn); */
                                continue;
                            }
                        }
                    }

                    var physicsObject;
                    if (kinematic) {
                        params.group = kinematicFilterFlag;
                        params.mask = collisionFilters;
                        params.kinematic = true;
                        physicsObject = physicsDevice.createCollisionObject(params);
                        if (physicsObject && !disabled) {
                            dynamicsWorld.addCollisionObject(physicsObject);
                        }
                    } else if (dynamic) {
                        params.mass = (fileModel.mass || 1);
                        params.inertia = fileModel.inertia;
                        params.group = dynamicFilterFlag;
                        params.mask = collisionFilters;
                        params.frozen = false;
                        if (fileModel.velocity) {
                            params.linearVelocity = fileModel.velocity;
                        }
                        if (fileModel.angularvelocity) {
                            params.angularVelocity = fileModel.angularvelocity;
                        }
                        physicsObject = physicsDevice.createRigidBody(params);
                        if (physicsObject && !disabled) {
                            dynamicsWorld.addRigidBody(physicsObject);
                        }
                    } else {
                        params.group = staticFilterFlag;
                        params.mask = collisionFilters;
                        physicsObject = physicsDevice.createCollisionObject(params);
                        if (physicsObject && !disabled) {
                            dynamicsWorld.addCollisionObject(physicsObject);
                        }
                    }

                    if (physicsObject) {
                        var physicsNode = {
                            body: physicsObject,
                            target: target
                        };

                        // Make the physics object point back at the target node so we can get to it
                        // from collision tests
                        physicsObject.userData = target;

                        if (origin) {
                            physicsNode.origin = origin;
                        }

                        if (triangleArray) {
                            physicsNode.triangleArray = triangleArray;
                        }

                        if (kinematic) {
                            physicsNode.kinematic = true;
                            target.kinematic = true;
                            target.dynamic = true;
                            kinematicPhysicsNodes.push(physicsNode);
                        } else if (dynamic) {
                            physicsNode.dynamic = true;
                            target.dynamic = true;
                            dynamicPhysicsNodes.push(physicsNode);
                        }

                        physicsNodes.push(physicsNode);

                        var targetPhysicsNodes = target.physicsNodes;
                        if (targetPhysicsNodes) {
                            targetPhysicsNodes.push(physicsNode);
                        } else {
                            target.physicsNodes = [physicsNode];
                            this.subscribeSceneNode(target);
                        }
                    }
                }
            }
        }
    };

    //
    // unsubscribeSceneNode
    //
    PhysicsManager.prototype.unsubscribeSceneNode = function (sceneNode) {
        sceneNode.unsubscribeCloned(this.sceneNodeCloned);
        sceneNode.unsubscribeDestroyed(this.sceneNodeDestroyed);
    };

    //
    // subscribeSceneNode
    //
    PhysicsManager.prototype.subscribeSceneNode = function (sceneNode) {
        sceneNode.subscribeCloned(this.sceneNodeCloned);
        sceneNode.subscribeDestroyed(this.sceneNodeDestroyed);
    };

    //
    // cloneSceneNode
    //
    PhysicsManager.prototype.cloneSceneNode = function (oldSceneNode, newSceneNode) {
        var physicsManager = this;

        var physicsManagerCloneNode = function physicsManagerCloneNodeFn(physicsNode, targetSceneNode) {
            var newPhysicsObject = physicsNode.body.clone();

            var newPhysicsNode = {
                body: newPhysicsObject,
                target: targetSceneNode
            };

            // Make the physics object point back at the target node so we can get to it
            // from collision tests
            newPhysicsObject.userData = targetSceneNode;

            if (physicsNode.origin) {
                newPhysicsNode.origin = physicsNode.origin;
            }

            if (physicsNode.triangleArray) {
                newPhysicsNode.triangleArray = physicsNode.triangleArray;
            }

            if (physicsNode.kinematic) {
                newPhysicsNode.kinematic = true;
                targetSceneNode.kinematic = true;
                targetSceneNode.dynamic = true;
                physicsManager.kinematicPhysicsNodes.push(newPhysicsNode);
                newPhysicsNode.body.transform = targetSceneNode.getWorldTransform();
            } else if (physicsNode.dynamic) {
                newPhysicsNode.dynamic = true;
                targetSceneNode.dynamic = true;
                physicsManager.dynamicPhysicsNodes.push(newPhysicsNode);
                newPhysicsNode.body.transform = targetSceneNode.getWorldTransform();
            }

            physicsManager.physicsNodes.push(newPhysicsNode);

            var targetPhysicsNodes = targetSceneNode.physicsNodes;
            if (targetPhysicsNodes) {
                targetPhysicsNodes.push(newPhysicsNode);
            } else {
                targetSceneNode.physicsNodes = [newPhysicsNode];
                this.subscribeSceneNode(targetSceneNode);
            }
        };

        var physicsNodes = oldSceneNode.physicsNodes;
        if (physicsNodes) {
            var numPhysicsNodes = physicsNodes.length;
            newSceneNode.physicsNodes = [];
            for (var p = 0; p < numPhysicsNodes; p += 1) {
                physicsManagerCloneNode(physicsNodes[p], newSceneNode);
            }
        }
    };

    //
    // Snapshot
    //
    PhysicsManager.prototype.createSnapshot = function () {
        var snapshot = {};

        // We only snapshot dynamic nodes because kinematics are driven externally
        var physicsNodes = this.dynamicPhysicsNodes;
        var numPhysicsNodes = physicsNodes.length;
        if (numPhysicsNodes > 0) {
            var physicsNode, n, body;
            for (n = 0; n < numPhysicsNodes; n += 1) {
                physicsNode = physicsNodes[n];
                body = physicsNode.body;
                snapshot[physicsNode.target.name] = {
                    active: body.active,
                    transform: body.transform,
                    linearVelocity: body.linearVelocity,
                    angularVelocity: body.angularVelocity
                };
            }
        }

        return snapshot;
    };

    PhysicsManager.prototype.restoreSnapshot = function (snapshot) {
        var physicsNodes = this.dynamicPhysicsNodes;
        var numPhysicsNodes = physicsNodes.length;
        if (numPhysicsNodes > 0) {
            var physicsNode, n, body, state;
            for (n = 0; n < numPhysicsNodes; n += 1) {
                physicsNode = physicsNodes[n];
                body = physicsNode.body;
                state = snapshot[physicsNode.target.name];
                if (state) {
                    body.transform = state.transform;
                    body.linearVelocity = state.linearVelocity;
                    body.angularVelocity = state.angularVelocity;
                    body.active = state.active;
                }
            }
        }
    };

    PhysicsManager.create = //
    // Constructor function
    //
    function (mathsDevice, physicsDevice, dynamicsWorld) {
        var physicsManager = new PhysicsManager();

        physicsManager.mathsDevice = mathsDevice;
        physicsManager.physicsDevice = physicsDevice;
        physicsManager.dynamicsWorld = dynamicsWorld;
        physicsManager.clear();

        physicsManager.sceneNodeCloned = function sceneNodeClonedFn(data) {
            physicsManager.cloneSceneNode(data.oldNode, data.newNode);
        };

        physicsManager.sceneNodeDestroyed = function sceneNodeDestroyedFn(data) {
            physicsManager.deleteNode(data.node);
        };

        physicsManager.tempMatrix = mathsDevice.m43BuildIdentity();

        return physicsManager;
    };
    PhysicsManager.version = 1;
    return PhysicsManager;
})();

PhysicsManager.prototype.arrayConstructor = Array;

// Detect correct typed arrays
((function () {
    if (typeof Float32Array !== "undefined") {
        var testArray = new Float32Array(4);
        var textDescriptor = Object.prototype.toString.call(testArray);
        if (textDescriptor === '[object Float32Array]') {
            PhysicsManager.prototype.arrayConstructor = Float32Array;
        }
    }
})());


// Copyright (c) 2010-2013 Turbulenz Limited
;

;

;

;

//
// VertexBufferManager
//
var VertexBufferManager = (function () {
    function VertexBufferManager() {
        this.maxVerticesPerVertexBuffer = 65535;
        this.numBuckets = 10;
    }
    //
    // bucket
    //
    VertexBufferManager.prototype.bucket = function (numVertices) {
        if (numVertices <= 64) {
            if (numVertices <= 16) {
                if (numVertices <= 8) {
                    return 0;
                }
                return 1;
            }

            if (numVertices <= 32) {
                return 2;
            }
            return 3;
        }

        if (numVertices <= 512) {
            if (numVertices <= 256) {
                if (numVertices <= 128) {
                    return 4;
                }
                return 5;
            }
            return 6;
        }

        if (numVertices <= 2048) {
            if (numVertices <= 1024) {
                return 7;
            }
            return 8;
        }
        return 9;
    };

    //
    // makeBuckets
    //
    VertexBufferManager.prototype.makeBuckets = function () {
        var result = [];

        for (var index = 0; index < this.numBuckets; index += 1) {
            result.push({ headChunk: null });
        }
        return result;
    };

    //
    // allocate
    //
    VertexBufferManager.prototype.allocate = function (numVertices, attributes) {
        var vertexbuffer = null;
        var baseIndex = 0;

        var vertexbufferParameters = {
            numVertices: undefined,
            attributes: attributes,
            dynamic: this.dynamicVertexBuffers
        };

        var poolIndex;
        var maxVerticesPerVertexBuffer = this.maxVerticesPerVertexBuffer;

        var attributesHash = '';
        var attributeIndex;
        var attribute;
        for (attributeIndex = 0; attributeIndex < attributes.length; attributeIndex += 1) {
            attribute = attributes[attributeIndex];
            if (attribute.name) {
                attributesHash += attribute.name;
            } else if (typeof attribute === "number") {
                attributesHash += attribute;
            } else {
                attributesHash += attribute.toString();
            }
            attributesHash += ',';
        }

        var numVertexBuffersPools = this.vertexBuffersPools.length;
        var vertexBuffersPool;

        for (poolIndex = 0; poolIndex < numVertexBuffersPools; poolIndex += 1) {
            if (this.vertexBuffersPools[poolIndex].attributesHash === attributesHash) {
                vertexBuffersPool = this.vertexBuffersPools[poolIndex];
                break;
            }
        }

        if (!vertexBuffersPool) {
            vertexBuffersPool = {
                attributesHash: attributesHash,
                vertexBufferData: []
            };
            this.vertexBuffersPools.push(vertexBuffersPool);
        }

        var vertexBufferData;
        if (numVertices < maxVerticesPerVertexBuffer) {
            for (var bucketIndex = this.bucket(numVertices); !vertexbuffer && bucketIndex < this.numBuckets; bucketIndex += 1) {
                var previousChunk;
                for (var vertexBufferIndex = 0; !vertexbuffer && (vertexBufferIndex < vertexBuffersPool.vertexBufferData.length); vertexBufferIndex += 1) {
                    vertexBufferData = vertexBuffersPool.vertexBufferData[vertexBufferIndex];

                    //Now find a to chunk allocate from
                    previousChunk = null;

                    for (var chunk = vertexBufferData.bucket[bucketIndex].headChunk; chunk; chunk = chunk.nextChunk) {
                        if (numVertices <= chunk.length) {
                            vertexbuffer = vertexBufferData.vertexBuffer;
                            baseIndex = chunk.baseIndex;
                            if (numVertices < chunk.length) {
                                chunk.baseIndex = (baseIndex + numVertices);
                                chunk.length -= numVertices;
                                var newBucketIndex = this.bucket(chunk.length);
                                if (newBucketIndex !== bucketIndex) {
                                    if (previousChunk) {
                                        previousChunk.nextChunk = chunk.nextChunk;
                                    } else {
                                        vertexBufferData.bucket[bucketIndex].headChunk = chunk.nextChunk;
                                    }

                                    //Add to new bucket
                                    chunk.nextChunk = vertexBufferData.bucket[newBucketIndex].headChunk;
                                    vertexBufferData.bucket[newBucketIndex].headChunk = chunk;
                                }
                            } else {
                                if (previousChunk) {
                                    previousChunk.nextChunk = chunk.nextChunk;
                                } else {
                                    vertexBufferData.bucket[bucketIndex].headChunk = chunk.nextChunk;
                                }
                                chunk.vertexBuffer = null;
                            }
                            break;
                        }
                        previousChunk = chunk;
                    }
                }
            }

            if (!vertexbuffer) {
                vertexbufferParameters.numVertices = maxVerticesPerVertexBuffer;
                vertexbuffer = this.graphicsDevice.createVertexBuffer(vertexbufferParameters);
                this.debugCreatedVertexBuffers += 1;

                /* debug.assert(vertexbuffer, "VertexBuffer not created."); */

                if (vertexbuffer) {
                    vertexBufferData = {
                        vertexBuffer: vertexbuffer,
                        bucket: this.makeBuckets()
                    };

                    vertexBufferData.bucket[this.bucket(maxVerticesPerVertexBuffer - numVertices)].headChunk = {
                        baseIndex: numVertices,
                        length: maxVerticesPerVertexBuffer - numVertices,
                        nextChunk: null
                    };

                    vertexBuffersPool.vertexBufferData.push(vertexBufferData);
                }
            }
        }

        if (!vertexbuffer) {
            vertexbufferParameters.numVertices = numVertices;
            vertexbuffer = this.graphicsDevice.createVertexBuffer(vertexbufferParameters);
            this.debugCreatedVertexBuffers += 1;

            /* debug.assert(vertexbuffer, "VertexBuffer not created."); */

            if (vertexbuffer) {
                vertexBuffersPool.vertexBufferData.push({
                    vertexBuffer: vertexbuffer,
                    bucket: this.makeBuckets()
                });
            }
        }

        return {
            vertexBuffer: vertexbuffer,
            baseIndex: baseIndex,
            length: numVertices,
            poolIndex: poolIndex
        };
    };

    //
    // free
    //
    VertexBufferManager.prototype.free = function (allocation) {
        var vertexBuffersPool = this.vertexBuffersPools[allocation.poolIndex];
        var vertexBufferData;
        for (var vertexBufferIndex = 0; vertexBufferIndex < vertexBuffersPool.vertexBufferData.length; vertexBufferIndex += 1) {
            if (allocation.vertexBuffer === vertexBuffersPool.vertexBufferData[vertexBufferIndex].vertexBuffer) {
                vertexBufferData = vertexBuffersPool.vertexBufferData[vertexBufferIndex];
                break;
            }
        }

        //TODO: optimise
        var leftChunk;
        var leftChunkPrevious;
        var rightChunk;
        var rightChunkPrevious;
        var previous;
        for (var bucketIndex = 0; !(leftChunk && rightChunk) && (bucketIndex < this.numBuckets); bucketIndex += 1) {
            previous = null;
            for (var chunk = vertexBufferData.bucket[bucketIndex].headChunk; chunk && !(leftChunk && rightChunk); chunk = chunk.nextChunk) {
                if (!leftChunk) {
                    if (chunk.baseIndex + chunk.length === allocation.baseIndex) {
                        leftChunk = chunk;
                        leftChunkPrevious = previous;
                    }
                }
                if (!rightChunk) {
                    if (chunk.baseIndex === allocation.baseIndex + allocation.length) {
                        rightChunk = chunk;
                        rightChunkPrevious = previous;
                    }
                }
                previous = chunk;
            }
        }

        var oldBucketIndex;
        var newBucketIndex;
        if (leftChunk && rightChunk) {
            oldBucketIndex = this.bucket(leftChunk.length);
            leftChunk.length += allocation.length + rightChunk.length;

            if (rightChunkPrevious) {
                rightChunkPrevious.nextChunk = rightChunk.nextChunk;
                if (rightChunk === leftChunkPrevious) {
                    leftChunkPrevious = rightChunkPrevious;
                }
            } else {
                vertexBufferData.bucket[this.bucket(rightChunk.length)].headChunk = rightChunk.nextChunk;
                if (rightChunk === leftChunkPrevious) {
                    leftChunkPrevious = null;
                }
            }

            //move left if it needs to
            newBucketIndex = this.bucket(leftChunk.length);
            if (newBucketIndex !== oldBucketIndex) {
                if (leftChunkPrevious) {
                    leftChunkPrevious.nextChunk = leftChunk.nextChunk;
                } else {
                    vertexBufferData.bucket[oldBucketIndex].headChunk = leftChunk.nextChunk;
                }

                //Add to new bucket
                leftChunk.nextChunk = vertexBufferData.bucket[newBucketIndex].headChunk;
                vertexBufferData.bucket[newBucketIndex].headChunk = leftChunk;
            }
        } else if (leftChunk) {
            oldBucketIndex = this.bucket(leftChunk.length);
            leftChunk.length += allocation.length;

            newBucketIndex = this.bucket(leftChunk.length);

            if (newBucketIndex !== oldBucketIndex) {
                if (leftChunkPrevious) {
                    leftChunkPrevious.nextChunk = leftChunk.nextChunk;
                } else {
                    vertexBufferData.bucket[oldBucketIndex].headChunk = leftChunk.nextChunk;
                }

                //Add to new bucket
                leftChunk.nextChunk = vertexBufferData.bucket[newBucketIndex].headChunk;
                vertexBufferData.bucket[newBucketIndex].headChunk = leftChunk;
            }
        } else if (rightChunk) {
            oldBucketIndex = this.bucket(rightChunk.length);
            rightChunk.baseIndex = allocation.baseIndex;
            rightChunk.length += allocation.length;

            newBucketIndex = this.bucket(rightChunk.length);

            if (newBucketIndex !== oldBucketIndex) {
                if (rightChunkPrevious) {
                    rightChunkPrevious.nextChunk = rightChunk.nextChunk;
                } else {
                    vertexBufferData.bucket[oldBucketIndex].headChunk = rightChunk.nextChunk;
                }

                //Add to new bucket
                rightChunk.nextChunk = vertexBufferData.bucket[newBucketIndex].headChunk;
                vertexBufferData.bucket[newBucketIndex].headChunk = rightChunk;
            }
        } else {
            var bucket = vertexBufferData.bucket[this.bucket(allocation.length)];
            bucket.headChunk = {
                baseIndex: allocation.baseIndex,
                length: allocation.length,
                nextChunk: bucket.headChunk
            };
        }

        //See if the whole thing is free and if so free the VB
        var lastChunk = vertexBufferData.bucket[this.numBuckets - 1].headChunk;
        if (lastChunk && lastChunk.length >= this.maxVerticesPerVertexBuffer) {
            vertexBuffersPool.vertexBufferData.splice(vertexBufferIndex, 1);
            vertexBufferData.vertexBuffer.destroy();
            vertexBufferData.vertexBuffer = null;
            vertexBufferData.bucket.length = 0;
            vertexBufferData.bucket = null;
        }
    };

    //
    // destroy
    //
    VertexBufferManager.prototype.destroy = function () {
        var vertexBuffersPools = this.vertexBuffersPools;
        if (vertexBuffersPools) {
            var numVertexBuffersPools = vertexBuffersPools.length;
            var i, j;
            for (i = 0; i < numVertexBuffersPools; i += 1) {
                var vertexBuffersPool = vertexBuffersPools[i];

                var vertexBufferDataArray = vertexBuffersPool.vertexBufferData;
                var numVertexBufferData = vertexBufferDataArray.length;
                for (j = 0; j < numVertexBufferData; j += 1) {
                    var vertexBufferData = vertexBufferDataArray[j];

                    var bucketArray = vertexBufferData.bucket;
                    if (bucketArray) {
                        bucketArray.length = 0;
                        vertexBufferData.bucket = null;
                    }

                    var vertexbuffer = vertexBufferData.vertexBuffer;
                    if (vertexbuffer) {
                        vertexbuffer.destroy();
                        vertexBufferData.vertexBuffer = null;
                    }
                }
                vertexBufferDataArray.length = 0;
            }
            vertexBuffersPools.length = 0;

            this.vertexBuffersPools = null;
        }

        this.graphicsDevice = null;
    };

    VertexBufferManager.create = //
    // create
    //
    function (graphicsDevice, dynamicVertexBuffers) {
        var manager = new VertexBufferManager();

        manager.vertexBuffersPools = [];
        manager.debugCreatedVertexBuffers = 0;
        manager.graphicsDevice = graphicsDevice;
        manager.dynamicVertexBuffers = dynamicVertexBuffers ? true : false;

        return manager;
    };
    VertexBufferManager.version = 1;
    return VertexBufferManager;
})();

// Copyright (c) 2010-2013 Turbulenz Limited
;

;

;

;

//
// IndexBufferManager
//
var IndexBufferManager = (function () {
    function IndexBufferManager() {
        this.maxIndicesPerIndexBuffer = 262144;
        this.numBuckets = 10;
    }
    //
    // bucket
    //
    IndexBufferManager.prototype.bucket = function (numIndices) {
        if (numIndices <= 64) {
            if (numIndices <= 16) {
                if (numIndices <= 8) {
                    return 0;
                }
                return 1;
            }

            if (numIndices <= 32) {
                return 2;
            }
            return 3;
        }

        if (numIndices <= 512) {
            if (numIndices <= 256) {
                if (numIndices <= 128) {
                    return 4;
                }
                return 5;
            }
            return 6;
        }

        if (numIndices <= 2048) {
            if (numIndices <= 1024) {
                return 7;
            }
            return 8;
        }
        return 9;
    };

    //
    // makeBuckets
    //
    IndexBufferManager.prototype.makeBuckets = function () {
        var result = [];

        for (var index = 0; index < this.numBuckets; index += 1) {
            result.push({ headChunk: null });
        }
        return result;
    };

    //
    // allocate
    //
    IndexBufferManager.prototype.allocate = function (numIndices, format) {
        var indexbuffer = null;
        var baseIndex = 0;

        if (typeof format === "string") {
            format = this.graphicsDevice['INDEXFORMAT_' + format];
        }

        var indexbufferParameters = {
            numIndices: undefined,
            format: format,
            dynamic: this.dynamicIndexBuffers
        };

        var poolIndex;
        var maxIndicesPerIndexBuffer = this.maxIndicesPerIndexBuffer;

        var numIndexBuffersPools = this.indexBuffersPools.length;
        var indexBuffersPool;

        for (poolIndex = 0; poolIndex < numIndexBuffersPools; poolIndex += 1) {
            if (this.indexBuffersPools[poolIndex].format === format) {
                indexBuffersPool = this.indexBuffersPools[poolIndex];
                break;
            }
        }

        if (!indexBuffersPool) {
            indexBuffersPool = {
                format: format,
                indexBufferData: []
            };
            this.indexBuffersPools.push(indexBuffersPool);
        }

        var indexBufferData;
        if (numIndices < maxIndicesPerIndexBuffer) {
            for (var bucketIndex = this.bucket(numIndices); !indexbuffer && bucketIndex < this.numBuckets; bucketIndex += 1) {
                var previousChunk;
                for (var indexBufferIndex = 0; !indexbuffer && (indexBufferIndex < indexBuffersPool.indexBufferData.length); indexBufferIndex += 1) {
                    indexBufferData = indexBuffersPool.indexBufferData[indexBufferIndex];

                    //Now find a to chunk allocate from
                    previousChunk = null;

                    for (var chunk = indexBufferData.bucket[bucketIndex].headChunk; chunk; chunk = chunk.nextChunk) {
                        if (numIndices <= chunk.length) {
                            indexbuffer = indexBufferData.indexBuffer;
                            baseIndex = chunk.baseIndex;
                            if (numIndices < chunk.length) {
                                chunk.baseIndex = (baseIndex + numIndices);
                                chunk.length -= numIndices;
                                var newBucketIndex = this.bucket(chunk.length);
                                if (newBucketIndex !== bucketIndex) {
                                    if (previousChunk) {
                                        previousChunk.nextChunk = chunk.nextChunk;
                                    } else {
                                        indexBufferData.bucket[bucketIndex].headChunk = chunk.nextChunk;
                                    }

                                    //Add to new bucket
                                    chunk.nextChunk = indexBufferData.bucket[newBucketIndex].headChunk;
                                    indexBufferData.bucket[newBucketIndex].headChunk = chunk;
                                }
                            } else {
                                if (previousChunk) {
                                    previousChunk.nextChunk = chunk.nextChunk;
                                } else {
                                    indexBufferData.bucket[bucketIndex].headChunk = chunk.nextChunk;
                                }
                                chunk.indexBuffer = null;
                            }
                            break;
                        }
                        previousChunk = chunk;
                    }
                }
            }

            if (!indexbuffer) {
                indexbufferParameters.numIndices = maxIndicesPerIndexBuffer;
                indexbuffer = this.graphicsDevice.createIndexBuffer(indexbufferParameters);
                this.debugCreatedIndexBuffers += 1;

                /* debug.assert(indexbuffer, "IndexBuffer not created."); */

                if (indexbuffer) {
                    indexBufferData = {
                        indexBuffer: indexbuffer,
                        bucket: this.makeBuckets()
                    };

                    indexBufferData.bucket[this.bucket(maxIndicesPerIndexBuffer - numIndices)].headChunk = {
                        baseIndex: numIndices,
                        length: maxIndicesPerIndexBuffer - numIndices,
                        nextChunk: null
                    };

                    indexBuffersPool.indexBufferData.push(indexBufferData);
                }
            }
        }

        if (!indexbuffer) {
            indexbufferParameters.numIndices = numIndices;
            indexbuffer = this.graphicsDevice.createIndexBuffer(indexbufferParameters);
            this.debugCreatedIndexBuffers += 1;

            /* debug.assert(indexbuffer, "IndexBuffer not created."); */

            if (indexbuffer) {
                indexBuffersPool.indexBufferData.push({
                    indexBuffer: indexbuffer,
                    bucket: this.makeBuckets()
                });
            }
        }

        return {
            indexBuffer: indexbuffer,
            baseIndex: baseIndex,
            length: numIndices,
            poolIndex: poolIndex
        };
    };

    //
    // free
    //
    IndexBufferManager.prototype.free = function (allocation) {
        var indexBuffersPool = this.indexBuffersPools[allocation.poolIndex];
        var indexBufferData;
        for (var indexBufferIndex = 0; indexBufferIndex < indexBuffersPool.indexBufferData.length; indexBufferIndex += 1) {
            if (allocation.indexBuffer === indexBuffersPool.indexBufferData[indexBufferIndex].indexBuffer) {
                indexBufferData = indexBuffersPool.indexBufferData[indexBufferIndex];
                break;
            }
        }

        //TODO: optimise
        var leftChunk;
        var leftChunkPrevious;
        var rightChunk;
        var rightChunkPrevious;
        var previous;
        for (var bucketIndex = 0; !(leftChunk && rightChunk) && (bucketIndex < this.numBuckets); bucketIndex += 1) {
            previous = null;
            for (var chunk = indexBufferData.bucket[bucketIndex].headChunk; chunk && !(leftChunk && rightChunk); chunk = chunk.nextChunk) {
                if (!leftChunk) {
                    if (chunk.baseIndex + chunk.length === allocation.baseIndex) {
                        leftChunk = chunk;
                        leftChunkPrevious = previous;
                    }
                }
                if (!rightChunk) {
                    if (chunk.baseIndex === allocation.baseIndex + allocation.length) {
                        rightChunk = chunk;
                        rightChunkPrevious = previous;
                    }
                }
                previous = chunk;
            }
        }

        var oldBucketIndex;
        var newBucketIndex;
        if (leftChunk && rightChunk) {
            oldBucketIndex = this.bucket(leftChunk.length);
            leftChunk.length += allocation.length + rightChunk.length;

            if (rightChunkPrevious) {
                rightChunkPrevious.nextChunk = rightChunk.nextChunk;
                if (rightChunk === leftChunkPrevious) {
                    leftChunkPrevious = rightChunkPrevious;
                }
            } else {
                indexBufferData.bucket[this.bucket(rightChunk.length)].headChunk = rightChunk.nextChunk;
                if (rightChunk === leftChunkPrevious) {
                    leftChunkPrevious = null;
                }
            }

            //move left if it needs to
            newBucketIndex = this.bucket(leftChunk.length);
            if (newBucketIndex !== oldBucketIndex) {
                if (leftChunkPrevious) {
                    leftChunkPrevious.nextChunk = leftChunk.nextChunk;
                } else {
                    indexBufferData.bucket[oldBucketIndex].headChunk = leftChunk.nextChunk;
                }

                //Add to new bucket
                leftChunk.nextChunk = indexBufferData.bucket[newBucketIndex].headChunk;
                indexBufferData.bucket[newBucketIndex].headChunk = leftChunk;
            }
        } else if (leftChunk) {
            oldBucketIndex = this.bucket(leftChunk.length);
            leftChunk.length += allocation.length;

            newBucketIndex = this.bucket(leftChunk.length);

            if (newBucketIndex !== oldBucketIndex) {
                if (leftChunkPrevious) {
                    leftChunkPrevious.nextChunk = leftChunk.nextChunk;
                } else {
                    indexBufferData.bucket[oldBucketIndex].headChunk = leftChunk.nextChunk;
                }

                //Add to new bucket
                leftChunk.nextChunk = indexBufferData.bucket[newBucketIndex].headChunk;
                indexBufferData.bucket[newBucketIndex].headChunk = leftChunk;
            }
        } else if (rightChunk) {
            oldBucketIndex = this.bucket(rightChunk.length);
            rightChunk.baseIndex = allocation.baseIndex;
            rightChunk.length += allocation.length;

            newBucketIndex = this.bucket(rightChunk.length);

            if (newBucketIndex !== oldBucketIndex) {
                if (rightChunkPrevious) {
                    rightChunkPrevious.nextChunk = rightChunk.nextChunk;
                } else {
                    indexBufferData.bucket[oldBucketIndex].headChunk = rightChunk.nextChunk;
                }

                //Add to new bucket
                rightChunk.nextChunk = indexBufferData.bucket[newBucketIndex].headChunk;
                indexBufferData.bucket[newBucketIndex].headChunk = rightChunk;
            }
        } else {
            var bucket = indexBufferData.bucket[this.bucket(allocation.length)];
            bucket.headChunk = {
                baseIndex: allocation.baseIndex,
                length: allocation.length,
                nextChunk: bucket.headChunk
            };
        }

        //See if the whole thing is free and if so free the VB
        var lastChunk = indexBufferData.bucket[this.numBuckets - 1].headChunk;
        if (lastChunk && lastChunk.length >= this.maxIndicesPerIndexBuffer) {
            indexBuffersPool.indexBufferData.splice(indexBufferIndex, 1);
            indexBufferData.indexBuffer.destroy();
            indexBufferData.indexBuffer = null;
            indexBufferData.bucket.length = 0;
            indexBufferData.bucket = null;
        }
    };

    //
    // destroy
    //
    IndexBufferManager.prototype.destroy = function () {
        var indexBuffersPools = this.indexBuffersPools;
        if (indexBuffersPools) {
            var numIndexBuffersPools = indexBuffersPools.length;
            var i, j;
            for (i = 0; i < numIndexBuffersPools; i += 1) {
                var indexBuffersPool = indexBuffersPools[i];

                var indexBufferDataArray = indexBuffersPool.indexBufferData;
                var numIndexBufferData = indexBufferDataArray.length;
                for (j = 0; j < numIndexBufferData; j += 1) {
                    var indexBufferData = indexBufferDataArray[j];

                    var bucketArray = indexBufferData.bucket;
                    if (bucketArray) {
                        bucketArray.length = 0;
                        indexBufferData.bucket = null;
                    }

                    var indexbuffer = indexBufferData.indexBuffer;
                    if (indexbuffer) {
                        indexbuffer.destroy();
                        indexBufferData.indexBuffer = null;
                    }
                }
                indexBufferDataArray.length = 0;
            }
            indexBuffersPools.length = 0;

            this.indexBuffersPools = null;
        }

        this.graphicsDevice = null;
    };

    IndexBufferManager.create = //
    // create
    //
    function (graphicsDevice, dynamicIndexBuffers) {
        var manager = new IndexBufferManager();

        manager.indexBuffersPools = [];
        manager.debugCreatedIndexBuffers = 0;
        manager.graphicsDevice = graphicsDevice;
        manager.dynamicIndexBuffers = dynamicIndexBuffers ? true : false;

        return manager;
    };
    IndexBufferManager.version = 1;
    return IndexBufferManager;
})();

// Copyright (c) 2009-2012 Turbulenz Limited
//
// MouseForces
//
var MouseForces = (function () {
    function MouseForces() {
    }
    MouseForces.prototype.generatePickRay = function (cameraTransform, viewWindowX, viewWindowY, aspectRatio, farPlane) {
        var md = this.md;
        var cam_right = md.m43Right(cameraTransform);
        var cam_up = md.m43Up(cameraTransform);
        var cam_at = md.v3Build(-cameraTransform[6], -cameraTransform[7], -cameraTransform[8]);
        var cam_pos = md.m43Pos(cameraTransform);

        this.X = this.mouseX;
        this.Y = this.mouseY;

        var x = (2.0 * this.X - 1.0) * viewWindowX;
        var y = (2.0 * this.Y - 1.0) * viewWindowY / aspectRatio;

        this.pickRayFrom = cam_pos;

        var direction = md.v3Normalize(md.v3Sub(md.v3Add(cam_at, md.v3ScalarMul(cam_right, x)), md.v3ScalarMul(cam_up, y)));
        this.pickRayTo = md.v3Add(cam_pos, md.v3ScalarMul(direction, farPlane));
    };

    MouseForces.prototype.update = function (dynamicsWorld, camera, force) {
        var md = this.md;
        if (this.grabBody) {
            this.generatePickRay(camera.matrix, 1.0 / camera.recipViewWindowX, 1.0 / camera.recipViewWindowY, camera.aspectRatio, camera.farPlane);

            if (this.pickedBody) {
                //keep it at the same picking distance
                var dir = md.v3Normalize(md.v3Sub(this.pickRayTo, this.pickRayFrom));
                var newPos = md.v3Add(this.pickRayFrom, md.v3ScalarMul(dir, this.oldPickingDist));
                if (this.dragExtentsMin) {
                    // If the user has supplied a bound for the dragging apply it
                    newPos = md.v3Max(newPos, this.dragExtentsMin);
                    newPos = md.v3Min(newPos, this.dragExtentsMax);
                }
                this.pickConstraint.pivotB = newPos;
                this.pickedBody.active = true;
            } else {
                //add a point to point constraint for picking
                var rayHit = dynamicsWorld.rayTest({
                    from: this.pickRayFrom,
                    to: this.pickRayTo,
                    mask: this.pickFilter
                });
                if (rayHit) {
                    var body = rayHit.body;
                    var pickPos = rayHit.hitPoint;

                    body.active = true;

                    this.pickedBody = body;

                    var localPivot = md.m43TransformPoint(md.m43InverseOrthonormal(body.transform), pickPos);

                    this.pickConstraint = this.pd.createPoint2PointConstraint({
                        bodyA: body,
                        pivotA: localPivot,
                        force: force,
                        damping: 0.5,
                        impulseClamp: this.clamp
                    });

                    dynamicsWorld.addConstraint(this.pickConstraint);

                    this.oldPickingDist = md.v3Length(md.v3Sub(pickPos, this.pickRayFrom));
                }
            }
        } else {
            if (this.pickedBody) {
                dynamicsWorld.removeConstraint(this.pickConstraint);
                this.pickConstraint = null;

                this.pickedBody = null;
            }
        }
    };

    MouseForces.create = // Constructor function
    function (gd, id, md, pd, dragExtentsMin, dragExtentsMax) {
        var c = new MouseForces();

        c.md = md;
        c.pd = pd;

        c.pickFilter = pd.FILTER_DYNAMIC;

        c.pickRayFrom = [0, 0, 0];
        c.pickRayTo = [0, 0, 0];

        c.clamp = 0;

        c.pickConstraint = null;
        c.pickedBody = null;

        c.oldPickingDist = 0;

        if (dragExtentsMin && dragExtentsMax) {
            c.dragExtentsMin = dragExtentsMin;
            c.dragExtentsMax = dragExtentsMax;
        }

        c.mouseX = 0.5;
        c.mouseY = 0.5;
        c.mouseZ = 0.0;
        c.X = 0.5;
        c.Y = 0.5;
        c.Z = 0.0;

        c.grabBody = false;

        // Mouse handling
        c.onmousewheel = function onmousewheelFn(delta) {
            c.mouseZ += delta;

            return false;
        };

        c.onmousemove = function onmousemoveFn(deltaX, deltaY) {
            c.mouseX += (deltaX / gd.width);
            c.mouseY += (deltaY / gd.height);

            return false;
        };

        c.onmousedown = function onmousedownFn() {
            c.mouseX = 0.5;
            c.mouseY = 0.5;
            c.mouseZ = 0.0;
            c.grabBody = true;
            return false;
        };

        c.onmouseup = function onmouseupFn() {
            c.mouseX = 0.5;
            c.mouseY = 0.5;
            c.mouseZ = 0.0;

            c.grabBody = false;
            return false;
        };

        id.addEventListener("mousewheel", c.onmousewheel);
        id.addEventListener("mousemove", c.onmousemove);
        id.addEventListener("mousedown", c.onmousedown);
        id.addEventListener("mouseup", c.onmouseup);

        return c;
    };
    MouseForces.version = 1;
    return MouseForces;
})();


// Copyright (c) 2011-2014 Turbulenz Limited
;

;

;

var RequestHandler = (function () {
    function RequestHandler() {
        this.reasonConnectionLost = 0;
        this.reasonServiceBusy = 1;
    }
    RequestHandler.prototype.retryExponential = function (callContext, requestFn, status) {
        if (!this.notifiedConnectionLost && TurbulenzEngine.time - this.connectionLostTime > (this.notifyTime * 0.001)) {
            this.notifiedConnectionLost = true;

            var reason;
            if (status === 0) {
                reason = this.reasonConnectionLost;
            } else {
                reason = this.reasonServiceBusy;
            }
            callContext.reason = reason;
            this.onRequestTimeout(reason, callContext);
        }

        if (this.connected) {
            this.connectionLostTime = TurbulenzEngine.time;
            this.notifiedConnectionLost = false;
            this.connected = false;
            this.reconnectTest = callContext;

            callContext.status = status;
        } else if (this.reconnectTest !== callContext) {
            var reconnectedObserver = this.reconnectedObserver;
            var onReconnected = function onReconnectedFn() {
                reconnectedObserver.unsubscribe(onReconnected);
                requestFn();
            };
            reconnectedObserver.subscribe(onReconnected);
            return;
        }

        if (callContext.expTime) {
            callContext.expTime = 2 * callContext.expTime;
            if (callContext.expTime > this.maxRetryTime) {
                callContext.expTime = this.maxRetryTime;
            }
        } else {
            callContext.expTime = this.initialRetryTime;
        }

        if (callContext.retries) {
            callContext.retries += 1;
        } else {
            callContext.retries = 1;
        }
        TurbulenzEngine.setTimeout(requestFn, callContext.expTime);
    };

    RequestHandler.prototype.retryAfter = function (callContext, retryAfter, requestFn, status) {
        if (callContext.retries) {
            callContext.retries += 1;
        } else {
            callContext.firstRetry = TurbulenzEngine.time;
            callContext.retries = 1;
        }

        if (!callContext.notifiedMaxRetries && TurbulenzEngine.time - callContext.firstRetry + retryAfter > this.notifyTime) {
            callContext.notifiedMaxRetries = true;

            var reason = this.reasonServiceBusy;
            callContext.reason = reason;
            this.onRequestTimeout(reason, callContext);
        }

        TurbulenzEngine.setTimeout(requestFn, retryAfter * 1000);
    };

    RequestHandler.prototype.request = function (callContext) {
        var makeRequest;
        var that = this;

        var responseCallback = function responseCallbackFn(responseAsset, status) {
            if (that.destroyed) {
                return;
            }

            var sendEventToHandlers = that.sendEventToHandlers;
            var handlers = that.handlers;

            if (status === 0 || status === 408 || status === 429 || status === 480 || status === 504) {
                that.retryExponential(callContext, makeRequest, status);
                return;
            }

            if (!that.connected) {
                // Reconnected!
                that.connected = true;
                if (that.reconnectTest === callContext && that.notifiedConnectionLost) {
                    that.onReconnected(that.reconnectTest.reason, that.reconnectTest);
                }
                that.reconnectTest = null;
                that.reconnectedObserver.notify();
            }

            if (callContext.responseFilter && !callContext.responseFilter.call(this, callContext, makeRequest, responseAsset, status)) {
                return;
            }

            if (that.responseFilter && !that.responseFilter(callContext, makeRequest, responseAsset, status)) {
                return;
            }

            if (callContext.onload) {
                var nameStr;
                if (responseAsset && responseAsset.name) {
                    nameStr = responseAsset.name;
                } else {
                    nameStr = callContext.src;
                }

                sendEventToHandlers(handlers.eventOnload, { eventType: "eventOnload", name: nameStr });

                callContext.onload(responseAsset, status, callContext);
                callContext.onload = null;
            }
            callContext = null;
        };

        makeRequest = function makeRequestFn() {
            if (that.destroyed) {
                return;
            }

            if (callContext.requestFn) {
                if (callContext.requestOwner) {
                    callContext.requestFn.call(callContext.requestOwner, callContext.src, responseCallback, callContext);
                } else {
                    callContext.requestFn(callContext.src, responseCallback, callContext);
                }
            } else if (callContext.requestOwner) {
                callContext.requestOwner.request(callContext.src, responseCallback, callContext);
            } else {
                TurbulenzEngine.request(callContext.src, responseCallback);
            }
        };

        makeRequest();
    };

    RequestHandler.prototype.addEventListener = function (eventType, eventListener) {
        var i;
        var length;
        var eventHandlers;

        if (this.handlers.hasOwnProperty(eventType)) {
            eventHandlers = this.handlers[eventType];

            if (eventListener) {
                // Check handler is not already stored
                length = eventHandlers.length;
                for (i = 0; i < length; i += 1) {
                    if (eventHandlers[i] === eventListener) {
                        // Event handler has already been added
                        return;
                    }
                }

                eventHandlers.push(eventListener);
            }
        }
    };

    RequestHandler.prototype.removeEventListener = function (eventType, eventListener) {
        var i;
        var length;
        var eventHandlers;

        if (this.handlers.hasOwnProperty(eventType)) {
            eventHandlers = this.handlers[eventType];

            if (eventListener) {
                length = eventHandlers.length;
                for (i = 0; i < length; i += 1) {
                    if (eventHandlers[i] === eventListener) {
                        eventHandlers.splice(i, 1);
                        break;
                    }
                }
            }
        }
    };

    RequestHandler.prototype.sendEventToHandlers = function (eventHandlers, arg0) {
        var i;
        var length = eventHandlers.length;

        if (length) {
            for (i = 0; i < length; i += 1) {
                eventHandlers[i](arg0);
            }
        }
    };

    RequestHandler.prototype.destroy = function () {
        this.destroyed = true;
        this.handlers = null;
        this.onReconnected = null;
        this.onRequestTimeout = null;
    };

    RequestHandler.create = function (params) {
        var rh = new RequestHandler();

        rh.initialRetryTime = params.initialRetryTime || 0.5 * 1000;
        rh.notifyTime = params.notifyTime || 4 * 1000;
        rh.maxRetryTime = params.maxRetryTime || 8 * 1000;

        rh.notifiedConnectionLost = false;
        rh.connected = true;
        rh.reconnectedObserver = Observer.create();
        rh.reconnectTest = null;

        /* tslint:disable:no-empty */
        rh.onReconnected = params.onReconnected || function onReconnectedFn() {
        };
        rh.onRequestTimeout = params.onRequestTimeout || function onRequestTimeoutFn(/* callContext */ ) {
        };

        /* tslint:enable:no-empty */
        var handlers = { eventOnload: [] };
        rh.handlers = handlers;

        return rh;
    };
    return RequestHandler;
})();

// Copyright (c) 2011-2013 Turbulenz Limited
;

;

var CustomMetricEvent = (function () {
    function CustomMetricEvent() {
    }
    CustomMetricEvent.create = function () {
        return new CustomMetricEvent();
    };
    return CustomMetricEvent;
})();
;

var CustomMetricEventBatch = (function () {
    function CustomMetricEventBatch() {
    }
    CustomMetricEventBatch.prototype.push = function (key, value) {
        var event = CustomMetricEvent.create();
        event.key = key;
        event.value = value;
        event.timeOffset = TurbulenzEngine.time;
        this.events.push(event);
    };

    CustomMetricEventBatch.prototype.length = function () {
        return this.events.length;
    };

    CustomMetricEventBatch.prototype.clear = function () {
        this.events.length = 0;
    };

    CustomMetricEventBatch.create = function () {
        var batch = new CustomMetricEventBatch();
        batch.events = [];
        return batch;
    };
    return CustomMetricEventBatch;
})();
;

;

;

// -----------------------------------------------------------------------------
// ServiceRequester
// -----------------------------------------------------------------------------
var ServiceRequester = (function () {
    function ServiceRequester() {
    }
    // make a request if the service is available. Same parameters as an
    // Utilities.ajax call with extra argument:
    //     neverDiscard - Never discard the request. Always queues the request
    //                    for when the service is again available. (Ignores
    //                    server preference)
    ServiceRequester.prototype.request = function (params) {
        var discardRequestFn = function discardRequestFn() {
            if (params.callback) {
                params.callback({ 'ok': false, 'msg': 'Service Unavailable. Discarding request' }, 503);
            }
        };

        var that = this;
        var serviceStatusObserver = this.serviceStatusObserver;

        var onServiceStatusChange;
        onServiceStatusChange = function onServiceStatusChangeFn(running, discardRequest) {
            if (discardRequest) {
                if (!params.neverDiscard) {
                    serviceStatusObserver.unsubscribe(onServiceStatusChange);
                    discardRequestFn();
                }
            } else if (running) {
                serviceStatusObserver.unsubscribe(onServiceStatusChange);
                that.request(params);
            }
        };

        if (!this.running) {
            if (this.discardRequests && !params.neverDiscard) {
                TurbulenzEngine.setTimeout(discardRequestFn, 0);
                return false;
            }

            if (!params.waiting) {
                params.waiting = true;
                serviceStatusObserver.subscribe(onServiceStatusChange);
            }
            return true;
        }

        var oldResponseFilter = params.responseFilter;
        params.responseFilter = function checkServiceUnavailableFn(callContext, makeRequest, responseJSON, status) {
            if (status === 503) {
                var responseObj = JSON.parse(responseJSON);
                var statusObj = responseObj.data;
                var discardRequests = (statusObj ? statusObj.discardRequests : true);
                that.discardRequests = discardRequests;

                if (discardRequests && !params.neverDiscard) {
                    discardRequestFn();
                } else {
                    serviceStatusObserver.subscribe(onServiceStatusChange);
                }
                TurbulenzServices.serviceUnavailable(that, callContext);

                // An error occurred so return false to avoid calling the success callback
                return false;
            } else {
                if (oldResponseFilter) {
                    return oldResponseFilter.call(params.requestHandler, callContext, makeRequest, responseJSON, status);
                }
                return true;
            }
        };

        Utilities.ajax(params);
        return true;
    };

    ServiceRequester.create = function (serviceName, params) {
        var serviceRequester = new ServiceRequester();

        if (!params) {
            params = {};
        }

        // we assume everything is working at first
        serviceRequester.running = true;
        serviceRequester.discardRequests = false;
        serviceRequester.serviceStatusObserver = Observer.create();

        serviceRequester.serviceName = serviceName;

        serviceRequester.onServiceUnavailable = params.onServiceUnavailable;
        serviceRequester.onServiceAvailable = params.onServiceAvailable;

        return serviceRequester;
    };
    return ServiceRequester;
})();
;

//
// TurbulenzServices
//
var TurbulenzServices = (function () {
    function TurbulenzServices() {
    }
    TurbulenzServices.available = function () {
        return window.gameSlug !== undefined;
    };

    TurbulenzServices.addBridgeEvents = function () {
        var turbulenz = window.top.Turbulenz;
        var turbulenzData = (turbulenz && turbulenz.Data) || {};
        var sessionToJoin = turbulenzData.joinMultiplayerSessionId;
        var that = this;

        var onJoinMultiplayerSession = function onJoinMultiplayerSessionFn(joinMultiplayerSessionId) {
            that.multiplayerJoinRequestQueue.push(joinMultiplayerSessionId);
        };

        var onReceiveConfig = function onReceiveConfigFn(configString) {
            var config = JSON.parse(configString);

            if (config.mode) {
                that.mode = config.mode;
            }

            if (config.joinMultiplayerSessionId) {
                that.multiplayerJoinRequestQueue.push(config.joinMultiplayerSessionId);
            }

            that.bridgeServices = !!config.bridgeServices;
        };

        if (sessionToJoin) {
            this.multiplayerJoinRequestQueue.push(sessionToJoin);
        }

        TurbulenzBridge.setOnMultiplayerSessionToJoin(onJoinMultiplayerSession);
        TurbulenzBridge.setOnReceiveConfig(onReceiveConfig);
        TurbulenzBridge.triggerRequestConfig();

        // Setup framework for asynchronous function calls
        this.responseHandlers = [null];

        // 0 is reserved value for no registered callback
        this.responseIndex = 0;
        TurbulenzBridge.on("bridgeservices.response", function (jsondata) {
            that.routeResponse(jsondata);
        });
    };

    TurbulenzServices.callOnBridge = function (event, data, callback) {
        var request = {
            data: data,
            key: undefined
        };
        if (callback) {
            this.responseIndex += 1;
            this.responseHandlers[this.responseIndex] = callback;
            request.key = this.responseIndex;
        }
        TurbulenzBridge.emit('bridgeservices.' + event, JSON.stringify(request));
    };

    TurbulenzServices.addSignature = function (data, url) {
        var str;
        data.requestUrl = url;
        str = TurbulenzEngine.encrypt(JSON.stringify(data));
        data.str = str;
        data.signature = TurbulenzEngine.generateSignature(str);
        return data;
    };

    TurbulenzServices.routeResponse = function (jsondata) {
        var response = JSON.parse(jsondata);
        var index = response.key || 0;
        var callback = this.responseHandlers[index];
        if (callback) {
            this.responseHandlers[index] = null;
            callback(response.data);
        }
    };

    TurbulenzServices.onServiceUnavailable = function (serviceName, callContext) {
    };

    TurbulenzServices.onServiceAvailable = function (serviceName, callContext) {
    };

    TurbulenzServices.createGameSession = function (requestHandler, sessionCreatedFn, errorCallbackFn) {
        return GameSession.create(requestHandler, sessionCreatedFn, errorCallbackFn);
    };

    TurbulenzServices.createMappingTable = function (requestHandler, gameSession, tableReceivedFn, defaultMappingSettings, errorCallbackFn) {
        var mappingTable;
        var mappingTableSettings = gameSession && gameSession.mappingTable;

        var mappingTableURL;
        var mappingTablePrefix;
        var assetPrefix;

        if (mappingTableSettings) {
            mappingTableURL = mappingTableSettings.mappingTableURL;
            mappingTablePrefix = mappingTableSettings.mappingTablePrefix;
            assetPrefix = mappingTableSettings.assetPrefix;
        } else if (defaultMappingSettings) {
            mappingTableURL = defaultMappingSettings.mappingTableURL || (defaultMappingSettings.mappingTableURL === "" ? "" : "mapping_table.json");
            mappingTablePrefix = defaultMappingSettings.mappingTablePrefix || (defaultMappingSettings.mappingTablePrefix === "" ? "" : "staticmax/");
            assetPrefix = defaultMappingSettings.assetPrefix || (defaultMappingSettings.assetPrefix === "" ? "" : "missing/");
        } else {
            mappingTableURL = "mapping_table.json";
            mappingTablePrefix = "staticmax/";
            assetPrefix = "missing/";
        }

        // If there is an error, inject any default mapping data and
        // inform the caller.
        var mappingTableErr = function mappingTableErrFn(msg) {
            var mapping = defaultMappingSettings && (defaultMappingSettings.urnMapping || {});
            var errorCallback = errorCallbackFn || TurbulenzServices.defaultErrorCallback;

            mappingTable.setMapping(mapping);
            errorCallback(msg);
        };

        var mappingTableParams = {
            mappingTableURL: mappingTableURL,
            mappingTablePrefix: mappingTablePrefix,
            assetPrefix: assetPrefix,
            requestHandler: requestHandler,
            onload: tableReceivedFn,
            errorCallback: mappingTableErr
        };

        mappingTable = MappingTable.create(mappingTableParams);
        return mappingTable;
    };

    TurbulenzServices.createLeaderboardManager = function (requestHandler, gameSession, leaderboardMetaReceived, errorCallbackFn) {
        return LeaderboardManager.create(requestHandler, gameSession, leaderboardMetaReceived, errorCallbackFn);
    };

    TurbulenzServices.createBadgeManager = function (requestHandler, gameSession) {
        return BadgeManager.create(requestHandler, gameSession);
    };

    TurbulenzServices.createStoreManager = function (requestHandler, gameSession, storeMetaReceived, errorCallbackFn) {
        return StoreManager.create(requestHandler, gameSession, storeMetaReceived, errorCallbackFn);
    };

    TurbulenzServices.createNotificationsManager = function (requestHandler, gameSession, successCallbackFn, errorCallbackFn) {
        return NotificationsManager.create(requestHandler, gameSession, successCallbackFn, errorCallbackFn);
    };

    TurbulenzServices.createMultiplayerSessionManager = function (requestHandler, gameSession) {
        return MultiPlayerSessionManager.create(requestHandler, gameSession);
    };

    TurbulenzServices.createUserProfile = function (requestHandler, profileReceivedFn, errorCallbackFn) {
        var userProfile = {};

        if (!errorCallbackFn) {
            errorCallbackFn = TurbulenzServices.defaultErrorCallback;
        }

        var loadUserProfileCallback = function loadUserProfileCallbackFn(userProfileData) {
            if (userProfileData && userProfileData.ok) {
                userProfileData = userProfileData.data;
                var p;
                for (p in userProfileData) {
                    if (userProfileData.hasOwnProperty(p)) {
                        userProfile[p] = userProfileData[p];
                    }
                }
            }
        };

        var url = '/api/v1/profiles/user';

        if (TurbulenzServices.available()) {
            this.getService('profiles').request({
                url: url,
                method: 'GET',
                callback: function createUserProfileAjaxErrorCheck(jsonResponse, status) {
                    if (status === 200) {
                        loadUserProfileCallback(jsonResponse);
                    } else if (errorCallbackFn) {
                        errorCallbackFn("TurbulenzServices.createUserProfile error with HTTP status " + status + ": " + jsonResponse.msg, status);
                    }
                    if (profileReceivedFn) {
                        profileReceivedFn(userProfile);
                    }
                },
                requestHandler: requestHandler
            });
        }

        return userProfile;
    };

    TurbulenzServices.upgradeAnonymousUser = // This should only be called if UserProfile.anonymous is true.
    function (upgradeCB) {
        if (upgradeCB) {
            var onUpgrade = function onUpgradeFn(_signal) {
                upgradeCB();
            };
            TurbulenzBridge.on('user.upgrade.occurred', onUpgrade);
        }

        TurbulenzBridge.emit('user.upgrade.show');
    };

    TurbulenzServices.sendCustomMetricEvent = function (eventKey, eventValue, requestHandler, gameSession, errorCallbackFn) {
        if (!errorCallbackFn) {
            errorCallbackFn = TurbulenzServices.defaultErrorCallback;
        }

        // defaultErrorCallback should never be null, so this should
        // hold.
        /* debug.assert(errorCallbackFn, "no error callback"); */

        if (!TurbulenzServices.available()) {
            errorCallbackFn("TurbulenzServices.sendCustomMetricEvent " + "failed: Service not available", 0);
            return;
        }

        if (('string' !== typeof eventKey) || (0 === eventKey.length)) {
            errorCallbackFn("TurbulenzServices.sendCustomMetricEvent " + "failed: Event key must be a non-empty string", 0);
            return;
        }

        if ('number' !== typeof eventValue || isNaN(eventValue) || !isFinite(eventValue)) {
            if ('[object Array]' !== Object.prototype.toString.call(eventValue)) {
                errorCallbackFn("TurbulenzServices.sendCustomMetricEvent " + "failed: Event value must be a number or " + "an array of numbers", 0);
                return;
            }

            var i, valuesLength = eventValue.length;
            for (i = 0; i < valuesLength; i += 1) {
                if ('number' !== typeof eventValue[i] || isNaN(eventValue[i]) || !isFinite(eventValue[i])) {
                    errorCallbackFn("TurbulenzServices.sendCustomMetricEvent " + "failed: Event value array elements must " + "be numbers", 0);
                    return;
                }
            }
        }

        this.getService('customMetrics').request({
            url: '/api/v1/custommetrics/add-event/' + gameSession.gameSlug,
            method: 'POST',
            data: {
                'key': eventKey,
                'value': eventValue,
                'gameSessionId': gameSession.gameSessionId
            },
            callback: function sendCustomMetricEventAjaxErrorCheck(jsonResponse, status) {
                if (status !== 200) {
                    errorCallbackFn("TurbulenzServices.sendCustomMetricEvent " + "error with HTTP status " + status + ": " + jsonResponse.msg, status);
                }
            },
            requestHandler: requestHandler,
            encrypt: true
        });
    };

    TurbulenzServices.sendCustomMetricEventBatch = function (eventBatch, requestHandler, gameSession, errorCallbackFn) {
        if (!errorCallbackFn) {
            errorCallbackFn = TurbulenzServices.defaultErrorCallback;
        }

        if (!TurbulenzServices.available()) {
            if (errorCallbackFn) {
                errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch failed: Service not available", 0);
            }
            return;
        }

        // Validation
        // Test eventBatch is correct type
        var currentTime = TurbulenzEngine.time;
        var events = eventBatch.events;
        var eventIndex;
        var numEvents = events.length;
        for (eventIndex = 0; eventIndex < numEvents; eventIndex += 1) {
            var eventKey = events[eventIndex].key;
            var eventValue = events[eventIndex].value;
            var eventTime = events[eventIndex].timeOffset;

            if (('string' !== typeof eventKey) || (0 === eventKey.length)) {
                if (errorCallbackFn) {
                    errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch failed: Event key must be a" + " non-empty string", 0);
                }
                return;
            }

            if ('number' !== typeof eventValue || isNaN(eventValue) || !isFinite(eventValue)) {
                if ('[object Array]' !== Object.prototype.toString.call(eventValue)) {
                    if (errorCallbackFn) {
                        errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch failed: Event value must be a" + " number or an array of numbers", 0);
                    }
                    return;
                }

                var i, valuesLength = eventValue.length;
                for (i = 0; i < valuesLength; i += 1) {
                    if ('number' !== typeof eventValue[i] || isNaN(eventValue[i]) || !isFinite(eventValue[i])) {
                        if (errorCallbackFn) {
                            errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch failed: Event value array" + " elements must be numbers", 0);
                        }
                        return;
                    }
                }
            }

            if ('number' !== typeof eventTime || isNaN(eventTime) || !isFinite(eventTime)) {
                if (errorCallbackFn) {
                    errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch failed: Event time offset is" + " corrupted", 0);
                }
                return;
            }

            // Update the time offset to be relative to the time we're sending the batch,
            // the server will use this to calculate event times
            events[eventIndex].timeOffset = eventTime - currentTime;
        }

        this.getService('customMetrics').request({
            url: '/api/v1/custommetrics/add-event-batch/' + gameSession.gameSlug,
            method: 'POST',
            data: { 'batch': events, 'gameSessionId': gameSession.gameSessionId },
            callback: function sendCustomMetricEventBatchAjaxErrorCheck(jsonResponse, status) {
                if (status !== 200 && errorCallbackFn) {
                    errorCallbackFn("TurbulenzServices.sendCustomMetricEventBatch error with HTTP status " + status + ": " + jsonResponse.msg, status);
                }
            },
            requestHandler: requestHandler,
            encrypt: true
        });
    };

    TurbulenzServices.getService = function (serviceName) {
        var services = this.services;
        if (services.hasOwnProperty(serviceName)) {
            return services[serviceName];
        } else {
            var service = ServiceRequester.create(serviceName);
            services[serviceName] = service;
            return service;
        }
    };

    TurbulenzServices.serviceUnavailable = function (service, callContext) {
        var waitingServices = this.waitingServices;
        var serviceName = service.serviceName;
        if (waitingServices.hasOwnProperty(serviceName)) {
            return;
        }

        waitingServices[serviceName] = service;

        service.running = false;

        var onServiceUnavailableCallbacks = function onServiceUnavailableCallbacksFn(service) {
            var onServiceUnavailable = callContext.onServiceUnavailable;
            if (onServiceUnavailable) {
                onServiceUnavailable.call(service, callContext);
            }
            if (service.onServiceUnavailable) {
                service.onServiceUnavailable();
            }
            if (TurbulenzServices.onServiceUnavailable) {
                TurbulenzServices.onServiceUnavailable(service);
            }
        };

        if (service.discardRequests) {
            onServiceUnavailableCallbacks(service);
        }

        if (this.pollingServiceStatus) {
            return;
        }

        var that = this;
        var pollServiceStatus;

        var serviceUrl = '/api/v1/service-status/game/read/' + window.gameSlug;
        var servicesStatusCB = function servicesStatusCBFn(responseObj, status) {
            if (status === 200) {
                var statusObj = responseObj.data;
                var servicesObj = statusObj.services;

                var retry = false;
                var serviceName;
                for (serviceName in waitingServices) {
                    if (waitingServices.hasOwnProperty(serviceName)) {
                        var service = waitingServices[serviceName];
                        var serviceData = servicesObj[serviceName];
                        var serviceRunning = serviceData.running;

                        service.running = serviceRunning;
                        service.description = serviceData.description;

                        if (serviceRunning) {
                            if (service.discardRequests) {
                                var onServiceAvailable = callContext.onServiceAvailable;
                                if (onServiceAvailable) {
                                    onServiceAvailable.call(service, callContext);
                                }
                                if (service.onServiceAvailable) {
                                    service.onServiceAvailable();
                                }
                                if (TurbulenzServices.onServiceAvailable) {
                                    TurbulenzServices.onServiceAvailable(service);
                                }
                            }

                            delete waitingServices[serviceName];
                            service.discardRequests = false;
                            service.serviceStatusObserver.notify(serviceRunning, service.discardRequests);
                        } else {
                            if (serviceData.discardRequests && !service.discardRequests) {
                                service.discardRequests = true;
                                onServiceUnavailableCallbacks(service);

                                // discard all waiting requests
                                service.serviceStatusObserver.notify(serviceRunning, service.discardRequests);
                            }
                            retry = true;
                        }
                    }
                }
                if (!retry) {
                    this.pollingServiceStatus = false;
                    return;
                }
                TurbulenzEngine.setTimeout(pollServiceStatus, statusObj.pollInterval * 1000);
            } else {
                TurbulenzEngine.setTimeout(pollServiceStatus, that.defaultPollInterval);
            }
        };

        pollServiceStatus = function pollServiceStatusFn() {
            Utilities.ajax({
                url: serviceUrl,
                method: 'GET',
                callback: servicesStatusCB
            });
        };

        pollServiceStatus();
    };
    TurbulenzServices.multiplayerJoinRequestQueue = {
        // A FIFO queue that passes events through to the handler when
        // un-paused and buffers up events while paused
        argsQueue: [],
        handler: function nopFn() {
        },
        context: undefined,
        paused: true,
        onEvent: function onEventFn(handler, context) {
            this.handler = handler;
            this.context = context;
        },
        push: function pushFn(sessionId) {
            var args = [sessionId];
            if (this.paused) {
                this.argsQueue.push(args);
            } else {
                this.handler.apply(this.context, args);
            }
        },
        shift: function shiftFn() {
            var args = this.argsQueue.shift();
            return args ? args[0] : undefined;
        },
        clear: function clearFn() {
            this.argsQueue = [];
        },
        pause: function pauseFn() {
            this.paused = true;
        },
        resume: function resumeFn() {
            this.paused = false;
            while (this.argsQueue.length) {
                this.handler.apply(this.context, this.argsQueue.shift());
                if (this.paused) {
                    break;
                }
            }
        }
    };

    TurbulenzServices.defaultErrorCallback = function (errorMsg, httpStatus) {
    };

    TurbulenzServices.services = {};
    TurbulenzServices.waitingServices = {};
    TurbulenzServices.pollingServiceStatus = false;

    TurbulenzServices.defaultPollInterval = 4000;
    return TurbulenzServices;
})();

if (typeof TurbulenzBridge !== 'undefined') {
    TurbulenzServices.addBridgeEvents();
} else {
    /* debug.log("No TurbulenzBridge object"); */
}

// Copyright (c) 2011-2013 Turbulenz Limited
/*global window: false*/
/*global TurbulenzServices: false*/
/*global debug: false*/
/*jshint nomen: false*/
/*
* An object that takes care of communication with the gamesite and, among
* other things, replaces the deprecated 'osdlib' module.
*
* It wraps an EventEmitter instance that is stored on the page and provides
* methods that manually display the 'loading'-flag, post certain events to
* the page or request information about a player's settings.
*/
var TurbulenzBridge = (function () {
    function TurbulenzBridge() {
    }
    TurbulenzBridge._initInstance = /**
    * Try to find an 'EventEmitter' object on the page and cache it.
    */
    function () {
        var Turbulenz = window.top.Turbulenz;

        if (Turbulenz && Turbulenz.Services) {
            var bridge = Turbulenz.Services.bridge;
            if (!bridge) {
                return;
            }

            this._bridge = bridge;

            this.emit = bridge.emit;

            // TODO can remove all of these or's after gamesite and hub updates
            this.on = bridge.gameListenerOn || bridge.addListener || bridge.setListener;

            // we cant use off yet because the function received on the other VM is re-wrapped each time
            // this.off = bridge.gameListenerOff;
            // Legacy functions addListener/setListener
            this.addListener = bridge.gameListenerOn || bridge.addListener || bridge.setListener;
            this.setListener = bridge.gameListenerOn || bridge.setListener;
        } else {
            /* debug.log("No turbulenz services"); */
        }

        if (typeof TurbulenzServices !== 'undefined') {
            TurbulenzServices.addBridgeEvents();
        }
    };

    TurbulenzBridge.isInitialised = function () {
        return (this._bridge !== undefined);
    };

    TurbulenzBridge.emit = function (serviceName, request, arg) {
    };

    TurbulenzBridge.on = function (serviceName, cb) {
    };

    TurbulenzBridge.addListener = //off: function offFn() {},
    function () {
    };

    TurbulenzBridge.setListener = function (eventName, listener) {
    };

    TurbulenzBridge.setOnReceiveConfig = /**
    * Message that passes game configuration information from the hosting site
    */
    function (callback) {
        this.on('config.set', callback);
    };

    TurbulenzBridge.triggerRequestConfig = function () {
        this.emit('config.request');
    };

    TurbulenzBridge.startLoading = /**
    * Methods to signal the beginning and end of load/save processes.
    * This will display hints to the player and helps the page
    * to prioritize resources.
    */
    function () {
        this.emit('status.loading.start');
    };

    TurbulenzBridge.startSaving = function () {
        this.emit('status.saving.start');
    };

    TurbulenzBridge.stopLoading = function () {
        this.emit('status.loading.stop');
    };

    TurbulenzBridge.stopSaving = function () {
        this.emit('status.saving.stop');
    };

    TurbulenzBridge.createdGameSession = /**
    * These methods tell the gamesite the gameSession so it can
    * emit a heartbeat for the message server to detect.
    * gameSessionId - A string for identifying the current game session
    */
    function (gameSessionId) {
        this.emit('game.session.created', gameSessionId);
    };

    TurbulenzBridge.destroyedGameSession = function (gameSessionId) {
        this.emit('game.session.destroyed', gameSessionId);
    };

    TurbulenzBridge.setGameSessionStatus = function (gameSessionId, status) {
        this.emit('game.session.status', gameSessionId, status);
    };

    TurbulenzBridge.setGameSessionInfo = function (info) {
        this.emit('game.session.info', info);
    };

    TurbulenzBridge.updateUserBadge = /**
    * Update a userbadge. Used by the BadgeManager
    */
    function (badge) {
        this.emit('userbadge.update', badge);
    };

    TurbulenzBridge.updateLeaderBoard = /**
    * Update a leaderboard. Used by the LeaderboardManager
    */
    function (scoreData) {
        this.emit('leaderboards.update', scoreData);
    };

    TurbulenzBridge.setOnMultiplayerSessionToJoin = /**
    * Handle multiplayer join events
    */
    function (callback) {
        this.on('multiplayer.session.join', callback);
    };

    TurbulenzBridge.triggerJoinedMultiplayerSession = function (session) {
        this.emit('multiplayer.session.joined', session);
    };

    TurbulenzBridge.triggerLeaveMultiplayerSession = function (sessionId) {
        this.emit('multiplayer.session.leave', sessionId);
    };

    TurbulenzBridge.triggerMultiplayerSessionMakePublic = function (sessionId) {
        this.emit('multiplayer.session.makepublic', sessionId);
    };

    TurbulenzBridge.setOnBasketUpdate = /**
    * Handle store basket events
    */
    function (callback) {
        this.on('basket.site.update', callback);
    };

    TurbulenzBridge.triggerBasketUpdate = function (basket) {
        this.emit('basket.game.update.v2', basket);
    };

    TurbulenzBridge.triggerUserStoreUpdate = function (items) {
        this.emit('store.user.update', items);
    };

    TurbulenzBridge.setOnPurchaseConfirmed = function (callback) {
        this.on('purchase.confirmed', callback);
    };

    TurbulenzBridge.setOnPurchaseRejected = function (callback) {
        this.on('purchase.rejected', callback);
    };

    TurbulenzBridge.triggerShowConfirmPurchase = function () {
        this.emit('purchase.show.confirm');
    };

    TurbulenzBridge.triggerFetchStoreMeta = function () {
        this.emit('fetch.store.meta');
    };

    TurbulenzBridge.setOnStoreMeta = function (callback) {
        this.on('store.meta.v2', callback);
    };

    TurbulenzBridge.triggerSendInstantNotification = /**
    * Handle in-game notification events
    */
    function (notification) {
        this.emit('notifications.ingame.sendInstant', notification);
    };

    TurbulenzBridge.triggerSendDelayedNotification = function (notification) {
        this.emit('notifications.ingame.sendDelayed', notification);
    };

    TurbulenzBridge.setOnNotificationSent = function (callback) {
        this.on('notifications.ingame.sent', callback);
    };

    TurbulenzBridge.triggerCancelNotificationByID = function (params) {
        this.emit('notifications.ingame.cancelByID', params);
    };

    TurbulenzBridge.triggerCancelNotificationsByKey = function (params) {
        this.emit('notifications.ingame.cancelByKey', params);
    };

    TurbulenzBridge.triggerCancelAllNotifications = function (params) {
        this.emit('notifications.ingame.cancelAll', params);
    };

    TurbulenzBridge.triggerInitNotificationManager = function (params) {
        this.emit('notifications.ingame.initNotificationManager', params);
    };

    TurbulenzBridge.setOnReceiveNotification = function (callback) {
        this.on('notifications.ingame.receive', callback);
    };

    TurbulenzBridge.changeAspectRatio = /**
    * Methods to signal changes of the viewport's aspect ratio to the page.
    */
    function (ratio) {
        this.emit('change.viewport.ratio', ratio);
    };

    TurbulenzBridge.setOnViewportHide = /**
    * Methods to set callbacks to react to events happening on the page.
    */
    function (callback) {
        this.on('change.viewport.hide', callback);
    };

    TurbulenzBridge.setOnViewportShow = function (callback) {
        this.on('change.viewport.show', callback);
    };

    TurbulenzBridge.setOnFullscreenOn = function (callback) {
        this.on('change.viewport.fullscreen.on', callback);
    };

    TurbulenzBridge.setOnFullscreenOff = function (callback) {
        this.on('change.viewport.fullscreen.off', callback);
    };

    TurbulenzBridge.setOnMenuStateChange = function (callback) {
        this.on('change.menu.state', callback);
    };

    TurbulenzBridge.setOnUserStateChange = function (callback) {
        this.on('change.user.state', callback);
    };

    TurbulenzBridge.triggerOnFullscreen = /**
    * Methods to send trigger event-emission on the page. These
    * prompt the page to trigger the aforementioned corresponding
    * onXXXX methods.
    */
    function () {
        this.emit('trigger.viewport.fullscreen');
    };

    TurbulenzBridge.triggerOnViewportVisibility = function () {
        this.emit('trigger.viewport.visibility');
    };

    TurbulenzBridge.triggerOnMenuStateChange = function () {
        this.emit('trigger.menu.state');
    };

    TurbulenzBridge.triggerOnUserStateChange = function () {
        this.emit('trigger.user.state');
    };

    TurbulenzBridge.queryFullscreen = /**
    * Methods to send requests for information to the page. These
    * methods can be used to send state-queries. They take a callback
    * function and prompt the page to call it.
    */
    /**
    * callback - a function that takes a single boolean value that
    * will be set to 'true' if the viewport is in fullscreen.
    */
    function (callback) {
        this.emit('query.viewport.fullscreen', callback);
    };

    TurbulenzBridge.queryViewportVisibility = /**
    * callback - a function that takes a single boolean value that
    * will be set to 'true' if the viewport is visible.
    */
    function (callback) {
        this.emit('query.viewport.visibility', callback);
    };

    TurbulenzBridge.queryMenuState = /**
    * callback - a function that takes an object-representation of
    * the current menu-state.
    */
    function (callback) {
        this.emit('query.menu.state', callback);
    };

    TurbulenzBridge.queryUserState = /**
    * callback - a function that takes an object-representation of
    * the current state of the user's settings.
    */
    function (callback) {
        this.emit('query.user.state', callback);
    };
    TurbulenzBridge._bridge = undefined;
    return TurbulenzBridge;
})();

if (!TurbulenzBridge.isInitialised()) {
    TurbulenzBridge._initInstance();
}

// Copyright (c) 2011-2012 Turbulenz Limited
;

;

var GameSession = (function () {
    function GameSession() {
        this.post_delay = 1000;
    }
    GameSession.prototype.setStatus = function (status) {
        if (this.destroyed || this.status === status) {
            return;
        }

        this.status = status;
        TurbulenzBridge.setGameSessionStatus(this.gameSessionId, status);
    };

    // callbackFn is for testing only!
    // It will not be called if destroy is called in TurbulenzEngine.onUnload
    GameSession.prototype.destroy = function (callbackFn) {
        var dataSpec;
        if (this.pendingUpdate) {
            TurbulenzEngine.clearTimeout(this.pendingUpdate);
            this.pendingUpdate = null;
        }

        if (!this.destroyed && this.gameSessionId) {
            // we can't wait for the callback as the browser doesn't
            // call async callbacks after onbeforeunload has been called
            TurbulenzBridge.destroyedGameSession(this.gameSessionId);
            this.destroyed = true;

            dataSpec = { 'gameSessionId': this.gameSessionId };

            if (TurbulenzServices.bridgeServices) {
                TurbulenzServices.callOnBridge('gamesession.destroy', dataSpec, callbackFn);
            } else {
                Utilities.ajax({
                    url: '/api/v1/games/destroy-session',
                    method: 'POST',
                    data: dataSpec,
                    callback: callbackFn,
                    requestHandler: this.requestHandler
                });
            }
        } else {
            if (callbackFn) {
                TurbulenzEngine.setTimeout(callbackFn, 0);
            }
        }
    };

    /**
    * Handle player metadata
    */
    GameSession.prototype.setTeamInfo = function (teamList) {
        var sessionData = this.info.sessionData;
        var oldTeamList = sessionData.teamList || [];
        if (teamList.join('#') !== oldTeamList.join('#')) {
            sessionData.teamList = teamList;
            this.update();
        }
    };

    GameSession.prototype.setPlayerInfo = function (playerId, data) {
        var playerData = this.info.playerSessionData[playerId];
        var key;
        var dirty = false;

        if (!playerData) {
            playerData = {};
            this.info.playerSessionData[playerId] = playerData;
            dirty = true;
        }

        for (key in data) {
            if (data.hasOwnProperty(key)) {
                if (!this.templatePlayerData.hasOwnProperty(key)) {
                    throw "unknown session data property " + key;
                }
                if (playerData[key] !== data[key]) {
                    playerData[key] = data[key];
                    dirty = true;
                }
            }
        }

        if (dirty) {
            this.update();
        }
    };

    GameSession.prototype.removePlayerInfo = function (playerId) {
        delete this.info.playerSessionData[playerId];
        this.update();
    };

    GameSession.prototype.clearAllPlayerInfo = function () {
        this.info.playerSessionData = {};
        this.update();
    };

    GameSession.prototype.update = function () {
        if (!this.pendingUpdate) {
            // Debounce the update to pick up any other changes.
            this.pendingUpdate = TurbulenzEngine.setTimeout(this.postData, this.post_delay);
        }
    };

    GameSession.create = function (requestHandler, sessionCreatedFn, errorCallbackFn) {
        var gameSession = new GameSession();
        var gameSlug = window.gameSlug;
        var turbulenz = window.top.Turbulenz;
        var turbulenzData = (turbulenz && turbulenz.Data) || {};
        var mode = turbulenzData.mode || TurbulenzServices.mode;
        var createSessionURL = '/api/v1/games/create-session/' + gameSlug;
        gameSession.info = {
            sessionData: {},
            playerSessionData: {}
        };

        gameSession.templatePlayerData = {
            team: null,
            color: null,
            status: null,
            rank: null,
            score: null,
            sortkey: null
        };

        gameSession.postData = function postDataFn() {
            TurbulenzBridge.setGameSessionInfo(JSON.stringify(gameSession.info));
            gameSession.pendingUpdate = null;
        };

        gameSession.pendingUpdate = null;

        gameSession.gameSlug = gameSlug;

        gameSession.requestHandler = requestHandler;
        gameSession.errorCallbackFn = errorCallbackFn || TurbulenzServices.defaultErrorCallback;
        gameSession.gameSessionId = null;
        gameSession.service = TurbulenzServices.getService('gameSessions');
        gameSession.status = null;

        if (!TurbulenzServices.available()) {
            if (sessionCreatedFn) {
                // On a timeout so it happens asynchronously, like an
                // ajax call.
                TurbulenzEngine.setTimeout(function sessionCreatedCall() {
                    sessionCreatedFn(gameSession);
                }, 0);
            }
            return gameSession;
        }

        var gameSessionRequestCallback = function gameSessionRequestCallbackFn(jsonResponse, status) {
            if (status === 200) {
                gameSession.mappingTable = jsonResponse.mappingTable;
                gameSession.gameSessionId = jsonResponse.gameSessionId;

                if (sessionCreatedFn) {
                    sessionCreatedFn(gameSession);
                }

                TurbulenzBridge.createdGameSession(gameSession.gameSessionId);
            } else if (404 === status) {
                // Treat this case as the equivalent of services being
                // unavailable.
                window.gameSlug = undefined;
                gameSession.gameSlug = undefined;

                if (sessionCreatedFn) {
                    sessionCreatedFn(gameSession);
                }
            } else {
                gameSession.errorCallbackFn("TurbulenzServices.createGameSession error with HTTP status " + status + ": " + jsonResponse.msg, status);
            }
        };

        if (mode) {
            createSessionURL += '/' + mode;
        }

        gameSession.service.request({
            url: createSessionURL,
            method: 'POST',
            callback: gameSessionRequestCallback,
            requestHandler: requestHandler,
            neverDiscard: true
        });

        return gameSession;
    };
    GameSession.version = 1;
    return GameSession;
})();

// Copyright (c) 2011 Turbulenz Limited
;
;

;

;

;

;

var MappingTable = (function () {
    function MappingTable() {
    }
    MappingTable.prototype.getURL = function (assetPath, missingCallbackFn) {
        var overrides = this.overrides;
        var profile = this.currentProfile;
        var override = overrides[profile];

        var url;
        while (override) {
            url = override.urnmapping[assetPath];
            if (url) {
                return url;
            }

            override = overrides[override.parent];
        }

        url = this.urlMapping[assetPath];
        if (url) {
            return url;
        } else {
            if (missingCallbackFn) {
                missingCallbackFn(assetPath);
            }
            return (this.assetPrefix + assetPath);
        }
    };

    // Overides and previously set mapping
    MappingTable.prototype.setMapping = function (mapping) {
        this.urlMapping = mapping;
    };

    MappingTable.prototype.map = function (logicalPath, physicalPath) {
        this.urlMapping[logicalPath] = physicalPath;
    };

    MappingTable.prototype.alias = function (alias, logicalPath) {
        var urlMapping = this.urlMapping;
        urlMapping[alias] = urlMapping[logicalPath];
    };

    MappingTable.prototype.getCurrentProfile = function () {
        return this.currentProfile;
    };

    MappingTable.prototype.setProfile = function (profile) {
        if (this.overrides.hasOwnProperty(profile)) {
            this.currentProfile = profile;
        } else {
            this.currentProfile = undefined;
        }
    };

    MappingTable.create = function (params) {
        var mappingTable = new MappingTable();

        mappingTable.mappingTableURL = params.mappingTableURL;
        mappingTable.tablePrefix = params.mappingTablePrefix;
        mappingTable.assetPrefix = params.assetPrefix;
        mappingTable.overrides = {};

        mappingTable.errorCallbackFn = params.errorCallback || TurbulenzServices.defaultErrorCallback;
        mappingTable.currentProfile = TurbulenzEngine.getSystemInfo().platformProfile;

        var onMappingTableLoad = function onMappingTableLoadFn(tableData) {
            var urlMapping = tableData.urnmapping || tableData.urnremapping || {};
            var overrides = tableData.overrides || {};

            mappingTable.urlMapping = urlMapping;
            mappingTable.overrides = overrides;

            // Prepend all the mapped physical paths with the asset server
            var tablePrefix = mappingTable.tablePrefix;
            if (tablePrefix) {
                var appendPrefix = function appendPrefix(map) {
                    var source;
                    for (source in map) {
                        if (map.hasOwnProperty(source)) {
                            map[source] = tablePrefix + map[source];
                        }
                    }
                };

                // Apply the prefix to the main runmapping table, and
                // any override tables.
                appendPrefix(urlMapping);
                var o;
                for (o in overrides) {
                    if (overrides.hasOwnProperty(o)) {
                        appendPrefix(overrides[o].urnmapping);
                    }
                }
            }

            params.onload(mappingTable);
        };

        if (!mappingTable.mappingTableURL) {
            if (params.mappingTableData) {
                TurbulenzEngine.setTimeout(function () {
                    onMappingTableLoad(JSON.parse(params.mappingTableData));
                }, 0);
            } else {
                TurbulenzEngine.setTimeout(function () {
                    mappingTable.errorCallbackFn("!! mappingtable params contain no url or data");
                }, 0);
            }
        } else {
            params.requestHandler.request({
                src: mappingTable.mappingTableURL,
                onload: function jsonifyResponse(jsonResponse, status) {
                    if (status === 200) {
                        var obj = JSON.parse(jsonResponse);
                        onMappingTableLoad(obj);
                    } else {
                        mappingTable.urlMapping = {};
                        jsonResponse = jsonResponse || { msg: "(no response)" };
                        mappingTable.errorCallbackFn("MappingTable.create: HTTP status " + status + ": " + jsonResponse.msg, status);
                    }
                }
            });
        }

        return mappingTable;
    };
    MappingTable.version = 1;
    return MappingTable;
})();

// Copyright (c) 2010-2011 Turbulenz Limited
;

;

/*global $*/
//
// HTML Controls
//
var HTMLControls = (function () {
    function HTMLControls() {
        this.version = 1;
    }
    HTMLControls.prototype.setSelectedRadio = function (groupName, index) {
        var controlGroup = this.radioControls[groupName];
        var control;
        if (controlGroup) {
            control = controlGroup.controls[index];
            if (control) {
                // Only set if the control at that index exists
                controlGroup.selected = index;
                this.updateRadio(control.id, true);
            }
        }
    };

    HTMLControls.prototype.addRadioControl = function (radioControlOptions) {
        var radioControls = this.radioControls;
        var groupName = radioControlOptions.groupName;
        var radioIndex = radioControlOptions.radioIndex;
        var radioID = radioControlOptions.id;

        //var value = radioControlOptions.value;
        var fn = radioControlOptions.fn;
        var isDefault = radioControlOptions.isDefault;

        if (groupName === undefined || groupName === null || radioIndex < 0 || radioID === undefined || radioID === null || fn === undefined || fn === null) {
            return;
        }

        var controlGroup = radioControls[groupName];
        if (!controlGroup) {
            radioControls[groupName] = {};
            controlGroup = radioControls[groupName];
            controlGroup.controls = [];
            controlGroup.selected = 0;
        }
        controlGroup.controls[radioIndex] = radioControlOptions;
        if (isDefault) {
            controlGroup.selected = radioIndex;
        }

        this.updateRadio(radioID, isDefault);
    };

    HTMLControls.prototype.addCheckboxControl = function (checkboxControlOptions) {
        var checkboxControls = this.checkboxControls;
        var id = checkboxControlOptions.id;
        var value = checkboxControlOptions.value;
        checkboxControls[value] = checkboxControlOptions;
        this.updateCheckbox(id, checkboxControlOptions.isSelected);
    };

    HTMLControls.prototype.addButtonControl = function (buttonControlOptions) {
        var buttonControls = this.buttonControls;
        var id = buttonControlOptions.id;
        buttonControls[id] = buttonControlOptions;
    };

    HTMLControls.prototype.addSliderControl = function (sliderControlOptions) {
        var sliderControls = this.sliderControls;
        sliderControls[sliderControlOptions.id] = sliderControlOptions;
    };

    HTMLControls.prototype.getSliderValue = function (id) {
        var value = window.$('#' + id).value;
        if (value) {
            return parseInt(value, 10);
        }
        return undefined;
    };

    HTMLControls.prototype.getHandler = function () {
        var radioControls = this.radioControls;
        var checkboxControls = this.checkboxControls;
        var buttonControls = this.buttonControls;
        return function (e) {
            var target;
            if (!e) {
                //IE
                e = window.event;
            }
            if (e.target) {
                //W3C
                target = e.target;
            } else if (e.srcElement) {
                //IE
                target = e.srcElement;
            }

            if (target.nodeType === 3) {
                // Safari fix
                target = target.parentNode;
            }

            var id = target.id;
            var control;
            switch (target.type) {
                case "radio":
                    for (var g in radioControls) {
                        if (radioControls.hasOwnProperty(g)) {
                            var index = 0;
                            var controls = radioControls[g].controls;
                            var length = controls.length;
                            while (index < length) {
                                control = controls[index];
                                if (control.id === id) {
                                    control.fn();
                                    return;
                                }
                                index += 1;
                            }
                        }
                    }
                    break;
                case "checkbox":
                    for (var c in checkboxControls) {
                        if (checkboxControls.hasOwnProperty(c)) {
                            control = checkboxControls[c];
                            if (control.id === id) {
                                var result = control.fn();

                                if (result !== undefined) {
                                    (document.getElementById(control.id)).checked = !!result;
                                }

                                return;
                            }
                        }
                    }
                    break;
                case "button":
                    for (var b in buttonControls) {
                        if (buttonControls.hasOwnProperty(b)) {
                            control = buttonControls[b];
                            if (b === id) {
                                control.fn();
                                return;
                            }
                        }
                    }
                    break;
                default:
            }
        };
    };

    HTMLControls.prototype.register = function () {
        var radioControls = this.radioControls;
        var checkboxControls = this.checkboxControls;
        var buttonControls = this.buttonControls;
        var sliderControls = this.sliderControls;
        var control, element, id;
        for (var g in radioControls) {
            if (radioControls.hasOwnProperty(g)) {
                var defaultIndex = radioControls[g].selected;
                var controls = radioControls[g].controls;
                var length = controls.length;
                for (var i = 0; i < length; i += 1) {
                    control = controls[i];
                    id = control.id;
                    element = document.getElementById(id);
                    if (element) {
                        element.value = control.value;
                        element.name = control.groupName;
                        element.onclick = this.getHandler();
                        element.checked = (defaultIndex === control.radioIndex) ? "checked" : "";
                    }
                }
            }
        }

        for (var c in checkboxControls) {
            if (checkboxControls.hasOwnProperty(c)) {
                control = checkboxControls[c];
                id = control.id;
                element = document.getElementById(id);
                if (element) {
                    element.value = control.value;
                    element.onclick = this.getHandler();
                    element.checked = control.isSelected ? "checked" : "";
                }
            }
        }

        for (var b in buttonControls) {
            if (buttonControls.hasOwnProperty(b)) {
                control = buttonControls[b];
                id = b;
                element = document.getElementById(id);
                if (element) {
                    element.value = control.value;
                    element.onclick = this.getHandler();
                }
            }
        }

        function createSliderCallback(id) {
            return function (event, ui) {
                var input = window.$("#" + id + "input");
                input.val(ui.value);
                input.change();
            };
        }

        function createInputCallback(control, id) {
            return function () {
                var value = parseFloat((this).value);

                if (!window.$) {
                    return;
                }
                var slider = window.$("#" + id);
                var input = window.$("#" + id + "input");
                if (!slider || !input) {
                    return;
                }
                var sliderVal = (slider.slider("value"));
                if (value === undefined || isNaN(value) || sliderVal === undefined || isNaN(sliderVal) || (sliderVal === value)) {
                    // Don't update if not changed
                    return;
                }

                var min = slider.slider("option", "min");
                var max = slider.slider("option", "max");
                if (value < min) {
                    value = min;
                } else if (value > max) {
                    value = max;
                }

                input.val(value);

                control.value = value;
                control.fn();
            };
        }

        for (var s in sliderControls) {
            if (sliderControls.hasOwnProperty(s)) {
                control = sliderControls[s];
                id = control.id;

                if (!window.$) {
                    return;
                }
                var slider = window.$("#" + id);
                var input = window.$("#" + id + "input");
                if (!slider || !input) {
                    return;
                }

                // Use jquery for sliders
                slider.slider({
                    value: control.value,
                    min: control.min,
                    max: control.max,
                    step: control.step,
                    slide: createSliderCallback(id)
                });

                input.val(slider.slider("value"));
                input.change(createInputCallback(control, id));
            }
        }

        this.registered = true;
    };

    HTMLControls.prototype.updateRadio = function (elementID, isSelected) {
        if (!this.registered) {
            return;
        }

        var element = (document.getElementById(elementID));
        if (element) {
            element.checked = !!isSelected;
        }
    };

    HTMLControls.prototype.updateCheckbox = function (elementID, isSelected) {
        if (!this.registered) {
            return;
        }

        var element = (document.getElementById(elementID));
        if (element) {
            element.checked = !!isSelected;
        }
    };

    HTMLControls.prototype.updateSlider = function (elementID, value) {
        if (!this.registered) {
            return;
        }

        if (window.$) {
            var element = window.$("#" + elementID);
            if (element) {
                element.slider("option", "value", value);
            }
        }
    };

    HTMLControls.create = // Constructor function
    function () {
        var c = new HTMLControls();
        c.radioControls = {};
        c.checkboxControls = {};
        c.buttonControls = {};
        c.sliderControls = {};
        c.registered = false;
        return c;
    };
    return HTMLControls;
})();

// Copyright (c) 2010-2011 Turbulenz Limited
//
//  SceneLoader
//  ===========
//
//  Helper class to load() a scene and wait for its dependencies before complete() is true
//
/*global TurbulenzEngine: false*/
/*global ResourceLoader: false*/
var SceneLoader = (function () {
    function SceneLoader() {
    }
    SceneLoader.prototype.complete = function () {
        if (!this.dependenciesLoaded) {
            if (this.sceneAssetsRequested && 0 === this.textureManager.getNumPendingTextures() && (!this.shaderManager || 0 === this.shaderManager.getNumPendingShaders())) {
                this.dependenciesLoaded = true;
            }
        }
        return this.dependenciesLoaded;
    };

    SceneLoader.prototype.load = function (parameters) {
        var sceneLoader = this;

        this.sceneAssetsRequested = false;

        this.scene = parameters.scene;
        this.assetPath = parameters.assetPath;
        this.textureManager = parameters.textureManager;
        this.shaderManager = parameters.shaderManager;
        this.effectManager = parameters.effectManager;
        this.animationManager = parameters.animationManager;
        this.requestHandler = parameters.requestHandler;

        if (parameters.keepLights !== undefined) {
            this.keepLights = parameters.keepLights;
        }
        if (parameters.keepCameras !== undefined) {
            this.keepCameras = parameters.keepCameras;
        }
        this.preSceneLoadFn = parameters.preSceneLoadFn;
        this.postSceneLoadFn = parameters.postSceneLoadFn;
        this.dependenciesLoaded = false;
        this.sceneLoaded = false;

        if (!parameters.append) {
            this.scene.clear();
        }

        var pathRemapping = this.pathRemapping;
        var requestHandler = this.requestHandler;
        function requestFn(url, onload) {
            requestHandler.request({
                src: (pathRemapping && pathRemapping[url]) || (this.pathPrefix + url),
                onload: onload
            });
        }

        if (parameters.request) {
            this.request = parameters.request;
        } else {
            this.request = requestFn;
        }

        // Create a callback for post scene load
        function loadSceneFinishedFn() {
            if (sceneLoader.postSceneLoadFn) {
                //Add custom logic
                sceneLoader.postSceneLoadFn(sceneLoader.scene);
            }
            sceneLoader.sceneAssetsRequested = true;
        }

        // Callback function to pass to the below request()
        function sceneReceivedFn(text/*, status */ ) {
            var sceneData = JSON.parse(text);

            if (!sceneData) {
                // Doesn't exist, create an empty data object to preload into the scene
                sceneData = {};
            }

            if (parameters.preSceneLoadFn) {
                //Add custom nodes
                parameters.preSceneLoadFn(sceneData);
            }

            // Set a yield callback for loading, for the sample don't do anything special
            function sceneLoadYieldFn(callback) {
                TurbulenzEngine.setTimeout(callback, 0);
            }

            function begetFn(o) {
                var F = function () {
                };
                F.prototype = o;
                return new F();
            }

            var loadParameters = begetFn(parameters);

            // Set a sceneLoad callback to load the resource into the scene
            function sceneLoadFn(resolvedData) {
                if (sceneLoader.animationManager) {
                    sceneLoader.animationManager.loadData(resolvedData);
                }

                loadParameters.data = resolvedData;
                loadParameters.yieldFn = sceneLoadYieldFn;
                loadParameters.onload = loadSceneFinishedFn;

                sceneLoader.scene.load(loadParameters);
            }

            // Create a resource loader to resolve any references in the scene data
            var resourceLoader = ResourceLoader.create();
            resourceLoader.resolve({
                data: sceneData,
                request: this.request,
                onload: sceneLoadFn,
                requestHandler: requestHandler
            });
        }

        this.request(parameters.assetPath, sceneReceivedFn);
    };

    SceneLoader.prototype.setPathRemapping = function (prm, assetUrl) {
        this.pathRemapping = prm;
        this.pathPrefix = assetUrl;
    };

    SceneLoader.create = function () {
        var sceneLoader = new SceneLoader();

        sceneLoader.scene = null;
        sceneLoader.assetPath = null;
        sceneLoader.textureManager = null;
        sceneLoader.shaderManager = null;
        sceneLoader.effectManager = null;
        sceneLoader.animationManager = null;
        sceneLoader.preSceneLoadFn = null;
        sceneLoader.postSceneLoadFn = null;
        sceneLoader.dependenciesLoaded = false;
        sceneLoader.sceneAssetsRequested = false;

        sceneLoader.pathPrefix = "";

        return sceneLoader;
    };
    return SceneLoader;
})();

/*global TurbulenzEngine: true */
/*global DefaultRendering: false */
/*global RequestHandler: false */
/*global SceneLoader: false */
/*global SceneNode: false */
/*global TurbulenzServices: false */
/*global TextureManager: false */
/*global ShaderManager: false */
/*global EffectManager: false */
/*global Scene: false */
/*global Camera: false */
/*global CameraController: false */
/*global Floor: false */
/*global MouseForces: false */
/*global PhysicsManager: false */
/*global HTMLControls: false */
TurbulenzEngine.onload = function onloadFn() {
    var errorCallback = function errorCallback(msg) {
        window.alert(msg);
    };
    TurbulenzEngine.onerror = errorCallback;

    var warningCallback = function warningCallback(msg) {
        window.alert(msg);
    };
    TurbulenzEngine.onwarning = warningCallback;

    var mathDeviceParameters = {};
    var mathDevice = TurbulenzEngine.createMathDevice(mathDeviceParameters);

    var graphicsDeviceParameters = {};
    var graphicsDevice = TurbulenzEngine.createGraphicsDevice(graphicsDeviceParameters);

    var physicsDeviceParameters = {};
    var physicsDevice = TurbulenzEngine.createPhysicsDevice(physicsDeviceParameters);

    var dynamicsWorldParameters = {};
    var dynamicsWorld = physicsDevice.createDynamicsWorld(dynamicsWorldParameters);

    var inputDeviceParameters = {};
    var inputDevice = TurbulenzEngine.createInputDevice(inputDeviceParameters);

    var requestHandlerParameters = {};
    var requestHandler = RequestHandler.create(requestHandlerParameters);

    var textureManager = TextureManager.create(graphicsDevice, requestHandler, null, errorCallback);
    var shaderManager = ShaderManager.create(graphicsDevice, requestHandler, null, errorCallback);
    var effectManager = EffectManager.create();
    var physicsManager = PhysicsManager.create(mathDevice, physicsDevice, dynamicsWorld);

    var mappingTable;
    var debugMode = true;

    // Renderer and assets for the scene.
    var renderer;
    var scene = Scene.create(mathDevice);
    var sceneLoader = SceneLoader.create();
    var duckMesh;

    // Setup world space
    var clearColor = mathDevice.v4Build(0.95, 0.95, 1.0, 1.0);
    var loadingClearColor = mathDevice.v4Build(0.8, 0.8, 0.8, 1.0);
    var worldUp = mathDevice.v3BuildYAxis();

    // Setup a camera to view a close-up object
    var camera = Camera.create(mathDevice);
    camera.nearPlane = 0.05;
    var cameraDefaultPos = mathDevice.v3Build(14.5, 8.0, 18.1);
    var cameraDefaultLook = mathDevice.v3Build(14.5, -(camera.farPlane / 2), -camera.farPlane);

    // The objects needed to draw the crosshair
    var technique2d;
    var shader2d;
    var techniqueParameters2d;
    var chSemantics = graphicsDevice.createSemantics(['POSITION']);
    var chFormats = [graphicsDevice.VERTEXFORMAT_FLOAT3];

    // The objects needed to draw the contact callbacks
    var contactsTechnique;
    var contactsShader;
    var contactsTechniqueParameters;
    var contactsSemantics = graphicsDevice.createSemantics(['POSITION']);
    var contactsFormats = [graphicsDevice.VERTEXFORMAT_FLOAT3];
    var contactWorldTransform = mathDevice.m43BuildIdentity();
    var contactWorldPoint = mathDevice.v3BuildZero();
    var contacts = [];
    var numContacts = 0;

    // Setup world floor
    var floor = Floor.create(graphicsDevice, mathDevice);
    var cameraController = CameraController.create(graphicsDevice, inputDevice, camera);

    // Mouse forces
    var dragMin = mathDevice.v3Build(-50, -50, -50);
    var dragMax = mathDevice.v3Build(50, 50, 50);
    var mouseForces = MouseForces.create(graphicsDevice, inputDevice, mathDevice, physicsDevice, dragMin, dragMax);
    mouseForces.clamp = 400;

    // Control codes
    var keyCodes = inputDevice.keyCodes;
    var mouseCodes = inputDevice.mouseCodes;

    // Setup the box firing objects, including the inertia
    var boxes = [];
    var numBoxes = 12;
    var fireCount = 0;
    var cubeExtents = mathDevice.v3Build(0.5, 0.5, 0.5);
    var boxShape = physicsDevice.createBoxShape({
        halfExtents: cubeExtents,
        margin: 0.001
    });

    var inertia = mathDevice.v3Copy(boxShape.inertia);
    inertia = mathDevice.v3ScalarMul(inertia, 1.0);

    function reset() {
        var halfPI = Math.PI / 2;
        var halfExtents = duckMesh.localHalfExtents;
        var yhalfExtent = halfExtents[1];
        var j = 1;

        function resetTransform(node, rotationMatrix) {
            var body = node.physicsNodes[0].body;
            dynamicsWorld.removeRigidBody(body);
            body.transform = mathDevice.m43BuildTranslation(j * 5, yhalfExtent, 0, body.transform);
            if (rotationMatrix) {
                body.transform = mathDevice.m43Mul(rotationMatrix, body.transform, body.transform);
            }
            body.linearVelocity = mathDevice.v3BuildZero();
            body.angularVelocity = mathDevice.v3BuildZero();
            body.active = true;
            dynamicsWorld.addRigidBody(body);
            j += 1;
        }
        ;

        // Reset ducks
        var rootNode = scene.findNode("DuckBoxPhys");
        resetTransform(rootNode);
        rootNode = scene.findNode("DuckConePhys");
        resetTransform(rootNode);
        rootNode = scene.findNode("DuckCylinderPhys");
        resetTransform(rootNode);
        rootNode = scene.findNode("DuckSpherePhys");
        resetTransform(rootNode);
        rootNode = scene.findNode("DuckCapsulePhys");
        var rot = mathDevice.m43FromAxisRotation(mathDevice.v3BuildXAxis(), halfPI);
        mathDevice.m43SetAxisRotation(rot, mathDevice.v3BuildZAxis(), halfPI);
        resetTransform(rootNode, rot);
        rootNode = scene.findNode("DuckConvexHullPhys");
        resetTransform(rootNode);

        // Reset boxes
        var count = 0;
        if (fireCount > 0 && fireCount < numBoxes) {
            count = fireCount;
        } else if (fireCount >= numBoxes) {
            count = numBoxes;
        }

        for (var i = 0; i < count; i += 1) {
            var box = boxes[i];
            var node = box.target;
            physicsManager.deletePhysicsNode(box);
            physicsManager.enableNode(node, false);
        }
        fireCount = 0;

        // Reset camera
        camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
        camera.updateViewMatrix();
    }

    function fireBox() {
        mouseForces.mouseX = 0.5;
        mouseForces.mouseY = 0.5;
        mouseForces.mouseZ = 0.0;
        mouseForces.generatePickRay(camera.matrix, 1.0 / camera.recipViewWindowX, 1.0 / camera.recipViewWindowY, camera.aspectRatio, camera.farPlane);

        var tr = mathDevice.m43BuildTranslation(mouseForces.pickRayFrom[0], mouseForces.pickRayFrom[1], mouseForces.pickRayFrom[2]);

        var linVel = mathDevice.v3Build(mouseForces.pickRayTo[0] - mouseForces.pickRayFrom[0], mouseForces.pickRayTo[1] - mouseForces.pickRayFrom[1], mouseForces.pickRayTo[2] - mouseForces.pickRayFrom[2]);
        mathDevice.v3Normalize(linVel, linVel);
        mathDevice.v3ScalarMul(linVel, 50.0, linVel);

        var box = boxes[fireCount % numBoxes];
        physicsManager.deletePhysicsNode(box);
        var node = box.target;
        var body = box.body;
        if (fireCount > numBoxes - 1) {
            physicsManager.enableNode(node, false);
        }
        body.transform = tr;
        body.angularVelocity = mathDevice.v3BuildZero();
        body.linearVelocity = linVel;
        body.active = true;

        physicsManager.physicsNodes.push(box);
        physicsManager.dynamicPhysicsNodes.push(box);
        physicsManager.enableNode(node, true);

        fireCount += 1;
    }

    var onMouseDown = function (button) {
        if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
            mouseForces.onmousedown();
        }
    };

    var onMouseUp = function (button) {
        if (mouseCodes.BUTTON_0 === button || mouseCodes.BUTTON_1 === button) {
            mouseForces.onmouseup();
        }
        if (mouseCodes.BUTTON_2 === button) {
            mouseForces.onmouseup();
            fireBox();
        }
    };

    var onKeyUp = function physicsOnkeyupFn(keynum) {
        if (keynum === keyCodes.R) {
            reset();
        }
        if (keynum === keyCodes.SPACE) {
            fireBox();
        } else {
            cameraController.onkeyup(keynum);
        }
    };

    // Add event listeners
    inputDevice.addEventListener("keyup", onKeyUp);
    inputDevice.addEventListener("mousedown", onMouseDown);
    inputDevice.addEventListener("mouseup", onMouseUp);

    // Controls
    var htmlControls = HTMLControls.create();
    htmlControls.addCheckboxControl({
        id: "checkbox01",
        value: "debugMode",
        isSelected: debugMode,
        fn: function () {
            debugMode = !debugMode;
            duckMesh.setDisabled(debugMode);
            return debugMode;
        }
    });
    htmlControls.register();

    function drawCrosshair() {
        if (!mouseForces.pickedBody) {
            graphicsDevice.setTechnique(technique2d);

            var screenWidth = graphicsDevice.width;
            var screenHeight = graphicsDevice.height;
            techniqueParameters2d.clipSpace = mathDevice.v4Build(2.0 / screenWidth, -2.0 / screenHeight, -1.0, 1.0);
            graphicsDevice.setTechniqueParameters(techniqueParameters2d);

            var writer = graphicsDevice.beginDraw(graphicsDevice.PRIMITIVE_LINES, 4, chFormats, chSemantics);

            if (writer) {
                var halfWidth = screenWidth * 0.5;
                var halfHeight = screenHeight * 0.5;
                writer([halfWidth - 10, halfHeight]);
                writer([halfWidth + 10, halfHeight]);
                writer([halfWidth, halfHeight - 10]);
                writer([halfWidth, halfHeight + 10]);

                graphicsDevice.endDraw(writer);
            }
        }
    }

    //function addContact(objectA, objectB, pairContact)
    //{
    //    if (debugMode)
    //    {
    //        objectB.calculateTransform(contactWorldTransform);
    //        mathDevice.m43TransformPoint(contactWorldTransform, pairContact.localPointOnB, contactWorldPoint);
    //        var contactNormal = pairContact.worldNormalOnB;
    //        if (numContacts >= contacts.length)
    //        {
    //            contacts[contacts.length] = new Float32Array(6);
    //        }
    //        var contact = contacts[numContacts];
    //        contact[0] = contactWorldPoint[0];
    //        contact[1] = contactWorldPoint[1];
    //        contact[2] = contactWorldPoint[2];
    //        contact[3] = contactWorldPoint[0] - contactNormal[0];
    //        contact[4] = contactWorldPoint[1] - contactNormal[1];
    //        contact[5] = contactWorldPoint[2] - contactNormal[2];
    //        numContacts += 1;
    //    }
    //}
    function addContacts(objectA, objectB, pairContacts) {
        if (debugMode) {
            var numPairContacts = pairContacts.length;
            var n;
            objectB.calculateTransform(contactWorldTransform);
            for (n = 0; n < numPairContacts; n += 1) {
                var pairContact = pairContacts[n];
                mathDevice.m43TransformPoint(contactWorldTransform, pairContact.localPointOnB, contactWorldPoint);
                var contactNormal = pairContact.worldNormalOnB;
                if (numContacts >= contacts.length) {
                    contacts[contacts.length] = new Float32Array(6);
                }
                var contact = contacts[numContacts];
                contact[0] = contactWorldPoint[0];
                contact[1] = contactWorldPoint[1];
                contact[2] = contactWorldPoint[2];
                contact[3] = contactWorldPoint[0] - contactNormal[0];
                contact[4] = contactWorldPoint[1] - contactNormal[1];
                contact[5] = contactWorldPoint[2] - contactNormal[2];
                numContacts += 1;
            }
        }
    }

    function drawContacts() {
        if (numContacts) {
            graphicsDevice.setTechnique(contactsTechnique);

            contactsTechniqueParameters.worldViewProjection = camera.viewProjectionMatrix;
            graphicsDevice.setTechniqueParameters(contactsTechniqueParameters);

            var writer = graphicsDevice.beginDraw(graphicsDevice.PRIMITIVE_LINES, numContacts * 2, contactsFormats, contactsSemantics);

            if (writer) {
                var n;
                for (n = 0; n < numContacts; n += 1) {
                    var contact = contacts[n];
                    writer(contact[0], contact[1], contact[2]);
                    writer(contact[3], contact[4], contact[5]);
                }

                graphicsDevice.endDraw(writer);
            }
        }
    }

	// Main game loop function
    var renderFrame = function renderFrameFn() {
        var currentTime = TurbulenzEngine.time;

        // Update input and camera
        inputDevice.update();

        if (mouseForces.pickedBody) {
            // If we're dragging a body don't apply the movement to the camera
            cameraController.pitch = 0;
            cameraController.turn = 0;
            cameraController.step = 0;
        }

        cameraController.update();

        var deviceWidth = graphicsDevice.width;
        var deviceHeight = graphicsDevice.height;
        var aspectRatio = (deviceWidth / deviceHeight);
        if (aspectRatio !== camera.aspectRatio) {
            camera.aspectRatio = aspectRatio;
            camera.updateProjectionMatrix();
        }
        camera.updateViewProjectionMatrix();

        numContacts = 0;

        // Update the physics
        mouseForces.update(dynamicsWorld, camera, 0.1);
        dynamicsWorld.update();

        physicsManager.update();
        scene.update();

        renderer.update(graphicsDevice, camera, scene, currentTime);

        if (graphicsDevice.beginFrame()) {
            if (renderer.updateBuffers(graphicsDevice, deviceWidth, deviceHeight)) {
                renderer.draw(graphicsDevice, clearColor);
                floor.render(graphicsDevice, camera);
                if (debugMode) {
                    scene.drawPhysicsNodes(graphicsDevice, shaderManager, camera, physicsManager);
                    scene.drawPhysicsGeometry(graphicsDevice, shaderManager, camera, physicsManager);
                    drawContacts();
                }
            }

            drawCrosshair();

            graphicsDevice.endFrame();
        }
    };

    var intervalID;
    var loadingLoop = function loadingLoopFn() {
        if (graphicsDevice.beginFrame()) {
            graphicsDevice.clear(loadingClearColor);
            graphicsDevice.endFrame();
        }

        if (sceneLoader.complete()) {
            TurbulenzEngine.clearInterval(intervalID);

            camera.lookAt(cameraDefaultLook, worldUp, cameraDefaultPos);
            camera.updateViewMatrix();

            renderer.updateShader(shaderManager);

            shader2d = shaderManager.get("shaders/generic2D.cgfx");
            technique2d = shader2d.getTechnique("constantColor2D");
            techniqueParameters2d = graphicsDevice.createTechniqueParameters({
                clipSpace: null,
                constantColor: mathDevice.v4Build(0, 0, 0, 1)
            });

            contactsShader = shaderManager.get("shaders/debug.cgfx");
            contactsTechnique = contactsShader.getTechnique("debug_lines_constant");
            contactsTechniqueParameters = graphicsDevice.createTechniqueParameters({
                worldViewProjection: null,
                constantColor: mathDevice.v4Build(1, 0, 0, 1)
            });

            if (physicsManager.physicsNodes.length >= 0) {
                // Floor is represented by a plane shape
                var floorShape = physicsDevice.createPlaneShape({
                    normal: mathDevice.v3Build(0, 1, 0),
                    distance: 0,
                    margin: 0.001
                });

                var floorObject = physicsDevice.createCollisionObject({
                    shape: floorShape,
                    transform: mathDevice.m43BuildIdentity(),
                    friction: 0.8,
                    restitution: 0.1,
                    group: physicsDevice.FILTER_STATIC,
                    mask: physicsDevice.FILTER_ALL,
                    //onPreSolveContact : addContact,
                    //onAddedContacts : addContacts
                    onProcessedContacts: addContacts
                });

                // Adds the floor collision object to the world
                dynamicsWorld.addCollisionObject(floorObject);
            }
            intervalID = TurbulenzEngine.setInterval(renderFrame, 1000 / 60);
        }
    };
    intervalID = TurbulenzEngine.setInterval(loadingLoop, 1000 / 10);

    // Change the clear color before we start loading assets
    loadingLoop();

    var postLoad = function postLoadFn() {
        var mass = 10.0;
        var margin = 0.001;
        //duckMesh = scene.findNode("DuckMesh");
		duckMesh = scene.findNode("Plane");
        var halfExtents = duckMesh.localHalfExtents;
        var xhalfExtent = halfExtents[0];
        var yhalfExtent = halfExtents[1];
        var halfPI = Math.PI / 2;
        var xAxis = mathDevice.v3BuildXAxis();
        var zAxis = mathDevice.v3BuildZAxis();

        function newPhysicsNode(name, shape, offsetTransform, pos) {
            var duckGeom = duckMesh.clone(name + "Geom");
            //physicsManager.deletePhysicsNode(duckGeom.physicsNodes[0]);
            duckGeom.physicsNodes = [];
            duckGeom.setLocalTransform(offsetTransform);

            var duckPhys = SceneNode.create({
                name: name + "Phys",
                local: pos,
                dynamic: true,
                disabled: false
            });

            var rigidBody = physicsDevice.createRigidBody({
                shape: shape,
                mass: mass,
                inertia: mathDevice.v3ScalarMul(shape.inertia, mass),
                transform: pos,
                friction: 0.7,
                restitution: 0.2,
                angularDamping: 0.4
            });

            var physicsNode = {
                body: rigidBody,
                target: duckPhys,
                dynamic: true
            };

            scene.addRootNode(duckPhys);
            duckPhys.addChild(duckGeom);
            duckPhys.physicsNodes = [physicsNode];
            duckPhys.setDynamic();

            physicsManager.physicsNodes.push(physicsNode);
            physicsManager.dynamicPhysicsNodes.push(physicsNode);
            physicsManager.enableHierarchy(duckPhys, true);
        }

        // Build a box duck
        var shape = physicsDevice.createBoxShape({
            halfExtents: halfExtents,
            margin: margin
        });

        var position = mathDevice.m43BuildTranslation(5, yhalfExtent, 0);
        newPhysicsNode("DuckBox", shape, mathDevice.m43BuildIdentity(), position);

        // Build a cone duck
        shape = physicsDevice.createConeShape({
            height: yhalfExtent * 2,
            radius: xhalfExtent,
            margin: margin
        });

        mathDevice.m43BuildTranslation(10, yhalfExtent, 0, position);
        newPhysicsNode("DuckCone", shape, mathDevice.m43BuildIdentity(), position);

        // Build a cylinder duck
        shape = physicsDevice.createCylinderShape({
            halfExtents: [xhalfExtent, yhalfExtent, xhalfExtent],
            margin: margin
        });

        mathDevice.m43BuildTranslation(15, yhalfExtent, 0, position);
        newPhysicsNode("DuckCylinder", shape, mathDevice.m43BuildIdentity(), position);

        // Build a sphere duck
        shape = physicsDevice.createSphereShape({
            radius: xhalfExtent,
            margin: margin
        });

        mathDevice.m43BuildTranslation(20, yhalfExtent, 0, position);
        newPhysicsNode("DuckSphere", shape, mathDevice.m43BuildIdentity(), position);

        // Build a capsule duck
        shape = physicsDevice.createCapsuleShape({
            radius: xhalfExtent,
            height: yhalfExtent * 2,
            margin: margin
        });

        // Capsules always take their height in the Y-axis
        // Rotate the capsule so it is flat against the floor
        // Rotate the duck so it is facing the correct direction
        mathDevice.m43BuildTranslation(25, yhalfExtent, 0, position);
        mathDevice.m43SetAxisRotation(position, xAxis, halfPI);
        mathDevice.m43SetAxisRotation(position, zAxis, halfPI);
        newPhysicsNode("DuckCapsule", shape, mathDevice.m43FromAxisRotation(zAxis, -halfPI), position);

        /* // Build a convex hull duck
        shape = physicsDevice.createConvexHullShape({
            points: duckMesh.physicsNodes[0].triangleArray.vertices,
            margin: margin,
            minExtent: mathDevice.v3Neg(halfExtents),
            maxExtent: halfExtents
        });

        mathDevice.m43BuildTranslation(30, yhalfExtent, 0, position);
        newPhysicsNode("DuckConvexHull", shape, mathDevice.m43BuildIdentity(), position); */

        // Set DuckMesh to disabled when debug rendering is enabled
        // This is to prevent Z-fighting between the geometry of the triangle mesh and asset
        duckMesh.setDisabled(true);

        // Create a pool of boxes
        var identity = mathDevice.m43BuildIdentity();
        for (var i = 0; i < numBoxes; i += 1) {
            var box = physicsDevice.createRigidBody({
                shape: boxShape,
                mass: 1.0,
                inertia: boxShape.inertia,
                transform: identity,
                friction: 0.9,
                restitution: 0.1
            });

            var newBox = SceneNode.create({
                name: "box" + i,
                local: identity,
                dynamic: true,
                disabled: false
            });
            var physicsNode = {
                body: box,
                target: newBox,
                dynamic: true
            };
            newBox.physicsNodes = [physicsNode];
            scene.addRootNode(newBox);
            boxes[i] = physicsNode;
        }
    };

    var loadAssets = function loadAssetsFn() {
        // Renderer for the scene.
        renderer = DefaultRendering.create(graphicsDevice, mathDevice, shaderManager, effectManager);

        renderer.setGlobalLightPosition(mathDevice.v3Build(0.5, 100.0, 0.5));
        renderer.setAmbientColor(mathDevice.v3Build(0.3, 0.3, 0.4));

        shaderManager.load("shaders/generic2D.cgfx");

        // Load mesh duck
        sceneLoader.load({
            scene: scene,
            //assetPath: "models/duck_trianglemesh.dae",
			assetPath: "models/iphone5.dae",
            graphicsDevice: graphicsDevice,
            textureManager: textureManager,
            effectManager: effectManager,
            shaderManager: shaderManager,
            physicsManager: physicsManager,
            requestHandler: requestHandler,
            baseMatrix: mathDevice.m43BuildTranslation(0, 0.77, 0),
            append: true,
            postSceneLoadFn: postLoad,
            dynamic: false
        });
    };

    var mappingTableReceived = function mappingTableReceivedFn(mappingTable) {
        textureManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
        shaderManager.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);
        sceneLoader.setPathRemapping(mappingTable.urlMapping, mappingTable.assetPrefix);

        loadAssets();
    };

    var gameSessionCreated = function gameSessionCreatedFn(gameSession) {
        mappingTable = TurbulenzServices.createMappingTable(requestHandler, gameSession, mappingTableReceived);
    };

    var gameSession = TurbulenzServices.createGameSession(requestHandler, gameSessionCreated);

    // Create a scene destroy callback to run when the window is closed
    function destroyScene() {
        gameSession.destroy();

        TurbulenzEngine.clearInterval(intervalID);
        clearColor = null;

        if (scene) {
            scene.destroy();
            scene = null;
        }
        requestHandler = null;

        if (renderer) {
            renderer.destroy();
            renderer = null;
        }

        camera = null;

        if (textureManager) {
            textureManager.destroy();
            textureManager = null;
        }

        if (shaderManager) {
            shaderManager.destroy();
            shaderManager = null;
        }

        effectManager = null;

        TurbulenzEngine.flush();
        graphicsDevice = null;
        mathDevice = null;
        physicsDevice = null;
        physicsManager = null;
        dynamicsWorld = null;
        mouseCodes = null;
        keyCodes = null;
        inputDevice = null;
        cameraController = null;
        floor = null;
    }

    TurbulenzEngine.onunload = destroyScene;
};
window.TurbulenzEngine = TurbulenzEngine;}());